{
 "info": {
  "name": "Verdant Dock API - main flows",
  "description": "Main flows of the Verdant Dock demonstration store (software-testing practice; nothing is real). Pick the environment (or set baseUrl and mailDomain) and run the whole collection in order. Request names are OpenAPI operationIds: see {{shopUrl}}/openapi.yaml and the step-by-step guide {{shopUrl}}/api-guide.md.",
  "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
 },
 "variable": [
  {
   "key": "baseUrl",
   "value": "https://api.verdant-v1.testthis.now"
  },
  {
   "key": "shopUrl",
   "value": "https://shop.verdant-v1.testthis.now"
  },
  {
   "key": "mailDomain",
   "value": "verdant-v1.testthis.now"
  },
  {
   "key": "password",
   "value": "Plant3r!Dock2026"
  }
 ],
 "item": [
  {
   "name": "1. Customer sign-up",
   "description": "New customer with a unique test address: sign-up, email code from the test mailbox, TOTP enrolment.",
   "item": [
    {
     "name": "signup - Register; emails a 6-digit verification code",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "signup"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/signup",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "signup"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"email\": \"{{email}}\",\n  \"password\": \"{{password}}\",\n  \"givenName\": \"Postman\",\n  \"familyName\": \"Tester\",\n  \"mobile\": \"0412 345 678\",\n  \"acceptTerms\": true\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `signup`. Register; emails a 6-digit verification code"
     },
     "event": [
      {
       "listen": "prerequest",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.collectionVariables.set('email', 'pm.' + Date.now() + '@' + pm.collectionVariables.get('mailDomain'));"
        ]
       }
      },
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 202', () => pm.response.to.have.status(202));"
        ]
       }
      }
     ]
    },
    {
     "name": "getLatestMail - verification code",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getLatestMail"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/test-mailbox/latest?to={{email}}&subject=verify&wait=10",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "test-mailbox",
        "latest"
       ],
       "query": [
        {
         "key": "to",
         "value": "{{email}}"
        },
        {
         "key": "subject",
         "value": "verify"
        },
        {
         "key": "wait",
         "value": "10"
        }
       ]
      },
      "description": "operationId `getLatestMail`. Latest mail received at an address on the stack's own domain (verification and reset codes)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const m = pm.response.json();",
         "pm.test('has a 6-digit code', () => pm.expect(m.code).to.match(/^\\d{6}$/));",
         "pm.collectionVariables.set('code', m.code);"
        ]
       }
      }
     ]
    },
    {
     "name": "verifyEmail - Confirm the emailed code; returns an enrolment token for TOTP setup",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "verifyEmail"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/verify-email",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "verify-email"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"email\": \"{{email}}\",\n  \"code\": \"{{code}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `verifyEmail`. Confirm the emailed code; returns an enrolment token for TOTP setup"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('enrolmentToken', pm.response.json().enrolmentToken);"
        ]
       }
      }
     ]
    },
    {
     "name": "startMfaEnrolment - Generate a TOTP secret (returns secret, otpauth URI and QR code SVG)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "startMfaEnrolment"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{enrolmentToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/mfa/enrol",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "mfa",
        "enrol"
       ]
      },
      "description": "operationId `startMfaEnrolment`. Generate a TOTP secret (returns secret, otpauth URI and QR code SVG)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('totpSecret', pm.response.json().secret);"
        ]
       }
      }
     ]
    },
    {
     "name": "activateMfa - Confirm a TOTP code; activates the account, returns 8 recovery codes and signs in",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "activateMfa"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{enrolmentToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/mfa/activate",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "mfa",
        "activate"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"code\": \"{{totp}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `activateMfa`. Confirm a TOTP code; activates the account, returns 8 recovery codes and signs in"
     },
     "event": [
      {
       "listen": "prerequest",
       "script": {
        "type": "text/javascript",
        "exec": [
         "function totp(secret, epochMs) {",
         "  const CryptoJS = require('crypto-js');",
         "  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';",
         "  let bits = '';",
         "  for (const c of String(secret).replace(/=+$/, '').toUpperCase()) { const v = alphabet.indexOf(c); if (v >= 0) bits += v.toString(2).padStart(5, '0'); }",
         "  let keyHex = '';",
         "  for (let i = 0; i + 8 <= bits.length; i += 8) keyHex += parseInt(bits.slice(i, i + 8), 2).toString(16).padStart(2, '0');",
         "  const counter = Math.floor(epochMs / 30000).toString(16).padStart(16, '0');",
         "  const h = CryptoJS.HmacSHA1(CryptoJS.enc.Hex.parse(counter), CryptoJS.enc.Hex.parse(keyHex)).toString();",
         "  const o = parseInt(h.slice(-1), 16);",
         "  return String((parseInt(h.substr(o * 2, 8), 16) & 0x7fffffff) % 1000000).padStart(6, '0');",
         "}",
         "// Sets {{totp}} for this request; waits for a new 30 s step if the last code used this one.",
         "function setTotp(secretVar) {",
         "  const last = Number(pm.collectionVariables.get('lastTotpStep_' + secretVar) || 0);",
         "  const now = Date.now();",
         "  const wait = Math.floor(now / 30000) <= last ? (last + 1) * 30000 - now + 1500 : 0;",
         "  setTimeout(() => {",
         "    const t = Date.now();",
         "    pm.collectionVariables.set('totp', totp(pm.collectionVariables.get(secretVar), t));",
         "    pm.collectionVariables.set('lastTotpStep_' + secretVar, String(Math.floor(t / 30000)));",
         "  }, wait);",
         "}",
         "setTotp('totpSecret');"
        ]
       }
      },
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const b = pm.response.json();",
         "pm.collectionVariables.set('accessToken', b.accessToken);",
         "pm.collectionVariables.set('refreshToken', b.refreshToken);",
         "pm.collectionVariables.set('recoveryCode', b.recoveryCodes[0]);"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "2. Sign in",
   "description": "Password, then TOTP (the script waits for a fresh 30 s step), then refresh.",
   "item": [
    {
     "name": "login - Step 1 - password",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "login"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/login",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "login"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"email\": \"{{email}}\",\n  \"password\": \"{{password}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `login`. Step 1 - password"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('MFA required', () => pm.expect(pm.response.json().status).to.eql('MFA_REQUIRED'));",
         "pm.collectionVariables.set('mfaToken', pm.response.json().mfaToken);"
        ]
       }
      }
     ]
    },
    {
     "name": "loginMfa - Step 2 - TOTP code or recovery code",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "loginMfa"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/login/mfa",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "login",
        "mfa"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"mfaToken\": \"{{mfaToken}}\",\n  \"code\": \"{{totp}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `loginMfa`. Step 2 - TOTP code or recovery code"
     },
     "event": [
      {
       "listen": "prerequest",
       "script": {
        "type": "text/javascript",
        "exec": [
         "function totp(secret, epochMs) {",
         "  const CryptoJS = require('crypto-js');",
         "  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';",
         "  let bits = '';",
         "  for (const c of String(secret).replace(/=+$/, '').toUpperCase()) { const v = alphabet.indexOf(c); if (v >= 0) bits += v.toString(2).padStart(5, '0'); }",
         "  let keyHex = '';",
         "  for (let i = 0; i + 8 <= bits.length; i += 8) keyHex += parseInt(bits.slice(i, i + 8), 2).toString(16).padStart(2, '0');",
         "  const counter = Math.floor(epochMs / 30000).toString(16).padStart(16, '0');",
         "  const h = CryptoJS.HmacSHA1(CryptoJS.enc.Hex.parse(counter), CryptoJS.enc.Hex.parse(keyHex)).toString();",
         "  const o = parseInt(h.slice(-1), 16);",
         "  return String((parseInt(h.substr(o * 2, 8), 16) & 0x7fffffff) % 1000000).padStart(6, '0');",
         "}",
         "// Sets {{totp}} for this request; waits for a new 30 s step if the last code used this one.",
         "function setTotp(secretVar) {",
         "  const last = Number(pm.collectionVariables.get('lastTotpStep_' + secretVar) || 0);",
         "  const now = Date.now();",
         "  const wait = Math.floor(now / 30000) <= last ? (last + 1) * 30000 - now + 1500 : 0;",
         "  setTimeout(() => {",
         "    const t = Date.now();",
         "    pm.collectionVariables.set('totp', totp(pm.collectionVariables.get(secretVar), t));",
         "    pm.collectionVariables.set('lastTotpStep_' + secretVar, String(Math.floor(t / 30000)));",
         "  }, wait);",
         "}",
         "setTotp('totpSecret');"
        ]
       }
      },
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const b = pm.response.json();",
         "pm.collectionVariables.set('accessToken', b.accessToken);",
         "pm.collectionVariables.set('refreshToken', b.refreshToken);"
        ]
       }
      }
     ]
    },
    {
     "name": "refreshToken - Exchange a refresh token for new tokens (rotates the refresh token)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "refreshToken"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/token/refresh",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "token",
        "refresh"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"refreshToken\": \"{{refreshToken}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `refreshToken`. Exchange a refresh token for new tokens (rotates the refresh token)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const b = pm.response.json();",
         "pm.collectionVariables.set('accessToken', b.accessToken);",
         "pm.collectionVariables.set('refreshToken', b.refreshToken);"
        ]
       }
      }
     ]
    },
    {
     "name": "getMe - Current customer",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getMe"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/me",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "me"
       ]
      },
      "description": "operationId `getMe`. Current customer"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('userId', pm.response.json().userId);"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "3. Truck order",
   "description": "AutoPlanter + matching plant, both insured (earns a free expert check-in), delivered by truck.",
   "item": [
    {
     "name": "listCatalogue - List products",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "listCatalogue"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/catalogue?type=PLANTER",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "catalogue"
       ],
       "query": [
        {
         "key": "type",
         "value": "PLANTER"
        }
       ]
      },
      "description": "operationId `listCatalogue`. List products"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "putCartItem - AutoPlanter Medium + Verdant Care 2 years",
     "request": {
      "method": "PUT",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "putCartItem"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cart/items/VD-AP-M",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cart",
        "items",
        "VD-AP-M"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"quantity\": 1,\n  \"coverPlan\": \"VC-2Y\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `putCartItem`. Set quantity (0-4, 0 removes) and optional cover plan for a SKU"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "putCartItem - Monstera Medium + Thrive Cover 1 year",
     "request": {
      "method": "PUT",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "putCartItem"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cart/items/VD-PL-MT-M",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cart",
        "items",
        "VD-PL-MT-M"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"quantity\": 1,\n  \"coverPlan\": \"TC-1Y\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `putCartItem`. Set quantity (0-4, 0 removes) and optional cover plan for a SKU"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "getCart - Cart with priced lines and rule violations (issues)",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getCart"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cart",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cart"
       ]
      },
      "description": "operationId `getCart`. Cart with priced lines and rule violations (issues)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('no cart issues', () => pm.expect(pm.response.json().issues).to.eql([]));"
        ]
       }
      }
     ]
    },
    {
     "name": "createAddress - Add an address (validated against Australian localities; service area assessed)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "createAddress"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/me/addresses",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "me",
        "addresses"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"label\": \"Home\",\n  \"line1\": \"12 Fern Street\",\n  \"suburb\": \"Glen Waverley\",\n  \"state\": \"VIC\",\n  \"postcode\": \"3150\",\n  \"phone\": \"0412 345 678\",\n  \"siteAccess\": \"GROUND\",\n  \"instructions\": \"Side gate, code 4821\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `createAddress`. Add an address (validated against Australian localities; service area assessed)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 201', () => pm.response.to.have.status(201));",
         "pm.collectionVariables.set('addressId', pm.response.json().addressId);"
        ]
       }
      }
     ]
    },
    {
     "name": "quoteCheckout - Full price (items, cover, freight, technician setup) and truck job plan for an address",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "quoteCheckout"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/checkout/quote",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "checkout",
        "quote"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"addressId\": \"{{addressId}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `quoteCheckout`. Full price (items, cover, freight, technician setup) and truck job plan for an address"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const q = pm.response.json();",
         "pm.test('can check out', () => pm.expect(q.canCheckout).to.eql(true));",
         "pm.collectionVariables.set('cartVersion', String(q.cartVersion));",
         "pm.collectionVariables.set('totalCents', String(q.totals.totalCents));"
        ]
       }
      }
     ]
    },
    {
     "name": "listDeliverySlots - Bookable delivery days and windows for the current cart and an address",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "listDeliverySlots"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/delivery/slots?addressId={{addressId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "delivery",
        "slots"
       ],
       "query": [
        {
         "key": "addressId",
         "value": "{{addressId}}"
        }
       ]
      },
      "description": "operationId `listDeliverySlots`. Bookable delivery days and windows for the current cart and an address"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const day = pm.response.json().days.find((d) => !d.closed && d.windows.some((w) => w.available));",
         "pm.test('a slot is free', () => pm.expect(day).to.be.ok);",
         "pm.collectionVariables.set('deliveryDate', day.date);",
         "pm.collectionVariables.set('deliveryWindow', day.windows.find((w) => w.available).code);"
        ]
       }
      }
     ]
    },
    {
     "name": "placeOrder - Place an order - books a truck, takes payment, emails a confirmation with a NOT A REAL TAX INVOICE PDF attached",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "placeOrder"
       },
       {
        "key": "Idempotency-Key",
        "value": "{{$guid}}"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"addressId\": \"{{addressId}}\",\n  \"cartVersion\": {{cartVersion}},\n  \"expectedTotalCents\": {{totalCents}},\n  \"delivery\": {\n    \"date\": \"{{deliveryDate}}\",\n    \"window\": \"{{deliveryWindow}}\"\n  },\n  \"payment\": {\n    \"nameOnCard\": \"Postman Tester\",\n    \"cardNumber\": \"4111 1111 1111 1111\",\n    \"expiryMonth\": \"12\",\n    \"expiryYear\": \"2030\",\n    \"cvv\": \"123\"\n  }\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `placeOrder`. Place an order - books a truck, takes payment, emails a confirmation with a NOT A REAL TAX INVOICE PDF attached"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 201', () => pm.response.to.have.status(201));",
         "const o = pm.response.json();",
         "pm.test('confirmed', () => pm.expect(o.status).to.eql('CONFIRMED'));",
         "pm.collectionVariables.set('orderId', o.orderId);",
         "pm.collectionVariables.set('orderNumber', o.orderNumber);",
         "pm.collectionVariables.set('invoiceNumber', o.invoice.invoiceNumber);"
        ]
       }
      }
     ]
    },
    {
     "name": "getOrder - One order",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getOrder"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders/{{orderId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders",
        "{{orderId}}"
       ]
      },
      "description": "operationId `getOrder`. One order"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "getInvoicePdf - The order's invoice PDF (marked NOT A REAL TAX INVOICE) - byte-for-byte the copy attached to the confirmation email",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getInvoicePdf"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders/{{orderId}}/invoice.pdf",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders",
        "{{orderId}}",
        "invoice.pdf"
       ]
      },
      "description": "operationId `getInvoicePdf`. The order's invoice PDF (marked NOT A REAL TAX INVOICE) - byte-for-byte the copy attached to the confirmation email"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('is a PDF', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/pdf'));"
        ]
       }
      }
     ]
    },
    {
     "name": "getLatestMail - order confirmation",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getLatestMail"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/test-mailbox/latest?to={{email}}&subject=confirmed&wait=10",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "test-mailbox",
        "latest"
       ],
       "query": [
        {
         "key": "to",
         "value": "{{email}}"
        },
        {
         "key": "subject",
         "value": "confirmed"
        },
        {
         "key": "wait",
         "value": "10"
        }
       ]
      },
      "description": "operationId `getLatestMail`. Latest mail received at an address on the stack's own domain (verification and reset codes)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const m = pm.response.json();",
         "pm.test('invoice attached', () => pm.expect(m.attachments[0].filename).to.eql(pm.collectionVariables.get('invoiceNumber') + '.pdf'));",
         "pm.collectionVariables.set('messageId', m.messageId);"
        ]
       }
      }
     ]
    },
    {
     "name": "getMailAttachment - invoice text",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getMailAttachment"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/test-mailbox/attachment?to={{email}}&messageId={{messageId}}&index=0&format=text",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "test-mailbox",
        "attachment"
       ],
       "query": [
        {
         "key": "to",
         "value": "{{email}}"
        },
        {
         "key": "messageId",
         "value": "{{messageId}}"
        },
        {
         "key": "index",
         "value": "0"
        },
        {
         "key": "format",
         "value": "text"
        }
       ]
      },
      "description": "operationId `getMailAttachment`. One attachment of a received message - the file, or (format=text, PDFs) its extracted text"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const t = pm.response.json().text;",
         "pm.test('says NOT A REAL TAX INVOICE', () => pm.expect(t).to.include('NOT A REAL TAX INVOICE'));",
         "pm.test('has the order number', () => pm.expect(t).to.include(pm.collectionVariables.get('orderNumber')));"
        ]
       }
      }
     ]
    },
    {
     "name": "updateDeliveryDetails - Change the recipient, contact phone or delivery instructions of a placed order",
     "request": {
      "method": "PATCH",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "updateDeliveryDetails"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders/{{orderId}}/delivery-details",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders",
        "{{orderId}}",
        "delivery-details"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"instructions\": \"Ring the bell twice. Park in the driveway.\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `updateDeliveryDetails`. Change the recipient, contact phone or delivery instructions of a placed order"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "4. Declined payment",
   "description": "A CVV ending in 0 is declined by the mock gateway: 402, nothing charged, truck released.",
   "item": [
    {
     "name": "putCartItem - AutoPlanter Small",
     "request": {
      "method": "PUT",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "putCartItem"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cart/items/VD-AP-S",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cart",
        "items",
        "VD-AP-S"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"quantity\": 1\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `putCartItem`. Set quantity (0-4, 0 removes) and optional cover plan for a SKU"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "placeOrder - declined (CVV ending 0)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "placeOrder"
       },
       {
        "key": "Idempotency-Key",
        "value": "{{$guid}}"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"addressId\": \"{{addressId}}\",\n  \"delivery\": {\n    \"date\": \"{{deliveryDate}}\",\n    \"window\": \"{{deliveryWindow}}\"\n  },\n  \"payment\": {\n    \"nameOnCard\": \"Postman Tester\",\n    \"cardNumber\": \"4111 1111 1111 1111\",\n    \"expiryMonth\": \"12\",\n    \"expiryYear\": \"2030\",\n    \"cvv\": \"120\"\n  }\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `placeOrder`. Place an order - books a truck, takes payment, emails a confirmation with a NOT A REAL TAX INVOICE PDF attached"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 402', () => pm.response.to.have.status(402));",
         "pm.test('PAYMENT_DECLINED', () => pm.expect(pm.response.json().code).to.eql('PAYMENT_DECLINED'));"
        ]
       }
      }
     ]
    },
    {
     "name": "clearCart - Empty the cart",
     "request": {
      "method": "DELETE",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "clearCart"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cart",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cart"
       ]
      },
      "description": "operationId `clearCart`. Empty the cart"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('cleared', () => pm.expect(pm.response.code).to.be.oneOf([200, 204]));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "5. Courier order",
   "description": "Books and accessories only: $25 courier to any Australian address, no delivery slot.",
   "item": [
    {
     "name": "putCartItem - AutoPlanter Coffee Table Book",
     "request": {
      "method": "PUT",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "putCartItem"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cart/items/VD-BK-CT",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cart",
        "items",
        "VD-BK-CT"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"quantity\": 1\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `putCartItem`. Set quantity (0-4, 0 removes) and optional cover plan for a SKU"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "createAddress - Perth (outside the truck area)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "createAddress"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/me/addresses",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "me",
        "addresses"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"label\": \"Perth office\",\n  \"line1\": \"10 Hay Street\",\n  \"suburb\": \"Perth\",\n  \"state\": \"WA\",\n  \"postcode\": \"6000\",\n  \"siteAccess\": \"LIFT\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `createAddress`. Add an address (validated against Australian localities; service area assessed)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 201', () => pm.response.to.have.status(201));",
         "pm.collectionVariables.set('perthAddressId', pm.response.json().addressId);"
        ]
       }
      }
     ]
    },
    {
     "name": "quoteCheckout - courier",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "quoteCheckout"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/checkout/quote",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "checkout",
        "quote"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"addressId\": \"{{perthAddressId}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `quoteCheckout`. Full price (items, cover, freight, technician setup) and truck job plan for an address"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const q = pm.response.json();",
         "pm.test('courier, $25', () => { pm.expect(q.delivery.method).to.eql('COURIER'); pm.expect(q.totals.deliveryCents).to.eql(2500); });"
        ]
       }
      }
     ]
    },
    {
     "name": "placeOrder - courier (no slot)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "placeOrder"
       },
       {
        "key": "Idempotency-Key",
        "value": "{{$guid}}"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"addressId\": \"{{perthAddressId}}\",\n  \"payment\": {\n    \"nameOnCard\": \"Postman Tester\",\n    \"cardNumber\": \"5555 5555 5555 4444\",\n    \"expiryMonth\": \"11\",\n    \"expiryYear\": \"2031\",\n    \"cvv\": \"321\"\n  }\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `placeOrder`. Place an order - books a truck, takes payment, emails a confirmation with a NOT A REAL TAX INVOICE PDF attached"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 201', () => pm.response.to.have.status(201));",
         "const o = pm.response.json();",
         "pm.test('tracking number', () => pm.expect(o.delivery.trackingNumber).to.match(/^SPC/));",
         "pm.collectionVariables.set('courierOrderId', o.orderId);"
        ]
       }
      }
     ]
    },
    {
     "name": "cancelOrder - courier, before dispatch",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "cancelOrder"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders/{{courierOrderId}}/cancel",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders",
        "{{courierOrderId}}",
        "cancel"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"reason\": \"Ordered by mistake\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `cancelOrder`. Cancel (up to 2 business days before delivery) - releases the truck and refunds in full"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('refunded', () => pm.expect(pm.response.json().status).to.eql('CANCELLED'));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "6. Test clock, cover and check-in",
   "description": "Move the business date past delivery for this session: the order shows DELIVERED (projected) and the cover ACTIVE. Nothing is saved.",
   "item": [
    {
     "name": "setTestClock - Move this session's business date n days ahead (resets at next login)",
     "request": {
      "method": "PUT",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "setTestClock"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/test-clock",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "test-clock"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"offsetDays\": {{offsetDays}}\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `setTestClock`. Move this session's business date n days ahead (resets at next login)"
     },
     "event": [
      {
       "listen": "prerequest",
       "script": {
        "type": "text/javascript",
        "exec": [
         "const today = new Date().toLocaleDateString('en-CA', { timeZone: 'Australia/Melbourne' });",
         "const days = Math.round((Date.parse(pm.collectionVariables.get('deliveryDate')) - Date.parse(today)) / 86400000) + 10;",
         "pm.collectionVariables.set('offsetDays', String(days));"
        ]
       }
      },
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "getOrder - projected DELIVERED",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getOrder"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/orders/{{orderId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "orders",
        "{{orderId}}"
       ]
      },
      "description": "operationId `getOrder`. One order"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('delivered (projected)', () => pm.expect(pm.response.json().status).to.eql('DELIVERED'));"
        ]
       }
      }
     ]
    },
    {
     "name": "listPolicies - Protection-plan policies (one per insured unit) with status for the business date",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "listPolicies"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/policies",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "policies"
       ]
      },
      "description": "operationId `listPolicies`. Protection-plan policies (one per insured unit) with status for the business date"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('two active policies', () => pm.expect(pm.response.json().summary.active).to.eql(2));"
        ]
       }
      }
     ]
    },
    {
     "name": "listClaims - Policies with their claim state (canClaim, claim), your claims, and showTab (true when any policy is ACTIVE on the business date or a claim exists)",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "listClaims"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/claims",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "claims"
       ]
      },
      "description": "operationId `listClaims`. Policies with their claim state (canClaim, claim), your claims, and showTab (true when any policy is ACTIVE on the business date or a claim exists)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.test('Claims tab shown', () => pm.expect(pm.response.json().showTab).to.eql(true));"
        ]
       }
      }
     ]
    },
    {
     "name": "resetTestClock - Reset this session to the real date",
     "request": {
      "method": "DELETE",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "resetTestClock"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/test-clock",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "test-clock"
       ]
      },
      "description": "operationId `resetTestClock`. Reset this session to the real date"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "7. Support case",
   "description": "Raise a case about the order, add a message, read it back, close it.",
   "item": [
    {
     "name": "createCase - Raise a case (201); emails you a copy",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "createCase"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cases",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cases"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"type\": \"SUPPORT\",\n  \"subject\": \"Which way should the dock face?\",\n  \"message\": \"Does the docking station need to face the window?\",\n  \"orderId\": \"{{orderId}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `createCase`. Raise a case (201); emails you a copy"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 201', () => pm.response.to.have.status(201));",
         "pm.collectionVariables.set('caseId', pm.response.json().caseId);"
        ]
       }
      }
     ]
    },
    {
     "name": "replyToCase - Add a message (reopens a closed case)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "replyToCase"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cases/{{caseId}}/messages",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cases",
        "{{caseId}}",
        "messages"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"message\": \"Also, can it go on carpet?\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `replyToCase`. Add a message (reopens a closed case)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "getCase - A case with its conversation",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getCase"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cases/{{caseId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cases",
        "{{caseId}}"
       ]
      },
      "description": "operationId `getCase`. A case with its conversation"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "closeCase - Close your case",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "closeCase"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/cases/{{caseId}}/close",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "cases",
        "{{caseId}}",
        "close"
       ]
      },
      "description": "operationId `closeCase`. Close your case"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "8. Staff portal",
   "description": "A new staff user on the same network sees the customer, their order and the case.",
   "item": [
    {
     "name": "staffEnrol - Staff enrolment; emails a 6-digit confirmation code (202)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffEnrol"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/enrol",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "enrol"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"email\": \"{{staffEmail}}\",\n  \"password\": \"{{password}}\",\n  \"givenName\": \"Postman\",\n  \"familyName\": \"Staff\",\n  \"jobTitle\": \"Customer service\",\n  \"acceptTerms\": true\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `staffEnrol`. Staff enrolment; emails a 6-digit confirmation code (202)"
     },
     "event": [
      {
       "listen": "prerequest",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.collectionVariables.set('staffEmail', 'pm.staff.' + Date.now() + '@' + pm.collectionVariables.get('mailDomain'));"
        ]
       }
      },
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 202', () => pm.response.to.have.status(202));"
        ]
       }
      }
     ]
    },
    {
     "name": "getLatestMail - staff enrolment code",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getLatestMail"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/test-mailbox/latest?to={{staffEmail}}&subject=code&wait=10",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "test-mailbox",
        "latest"
       ],
       "query": [
        {
         "key": "to",
         "value": "{{staffEmail}}"
        },
        {
         "key": "subject",
         "value": "code"
        },
        {
         "key": "wait",
         "value": "10"
        }
       ]
      },
      "description": "operationId `getLatestMail`. Latest mail received at an address on the stack's own domain (verification and reset codes)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('staffCode', pm.response.json().code);"
        ]
       }
      }
     ]
    },
    {
     "name": "staffVerifyEmail - Confirm the enrolment code; returns an enrolmentToken (scope staff_mfa_enrol)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffVerifyEmail"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/verify-email",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "verify-email"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"email\": \"{{staffEmail}}\",\n  \"code\": \"{{staffCode}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `staffVerifyEmail`. Confirm the enrolment code; returns an enrolmentToken (scope staff_mfa_enrol)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('staffEnrolmentToken', pm.response.json().enrolmentToken);"
        ]
       }
      }
     ]
    },
    {
     "name": "staffStartMfaEnrolment - Start TOTP enrolment (secret, otpauth URI, QR SVG)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffStartMfaEnrolment"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{staffEnrolmentToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/mfa/enrol",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "mfa",
        "enrol"
       ]
      },
      "description": "operationId `staffStartMfaEnrolment`. Start TOTP enrolment (secret, otpauth URI, QR SVG)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('staffTotpSecret', pm.response.json().secret);"
        ]
       }
      }
     ]
    },
    {
     "name": "staffActivateMfa - Activate TOTP; returns staff tokens and recovery codes",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "Content-Type",
        "value": "application/json"
       },
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffActivateMfa"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{staffEnrolmentToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/mfa/activate",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "mfa",
        "activate"
       ]
      },
      "body": {
       "mode": "raw",
       "raw": "{\n  \"code\": \"{{totp}}\"\n}",
       "options": {
        "raw": {
         "language": "json"
        }
       }
      },
      "description": "operationId `staffActivateMfa`. Activate TOTP; returns staff tokens and recovery codes"
     },
     "event": [
      {
       "listen": "prerequest",
       "script": {
        "type": "text/javascript",
        "exec": [
         "function totp(secret, epochMs) {",
         "  const CryptoJS = require('crypto-js');",
         "  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';",
         "  let bits = '';",
         "  for (const c of String(secret).replace(/=+$/, '').toUpperCase()) { const v = alphabet.indexOf(c); if (v >= 0) bits += v.toString(2).padStart(5, '0'); }",
         "  let keyHex = '';",
         "  for (let i = 0; i + 8 <= bits.length; i += 8) keyHex += parseInt(bits.slice(i, i + 8), 2).toString(16).padStart(2, '0');",
         "  const counter = Math.floor(epochMs / 30000).toString(16).padStart(16, '0');",
         "  const h = CryptoJS.HmacSHA1(CryptoJS.enc.Hex.parse(counter), CryptoJS.enc.Hex.parse(keyHex)).toString();",
         "  const o = parseInt(h.slice(-1), 16);",
         "  return String((parseInt(h.substr(o * 2, 8), 16) & 0x7fffffff) % 1000000).padStart(6, '0');",
         "}",
         "// Sets {{totp}} for this request; waits for a new 30 s step if the last code used this one.",
         "function setTotp(secretVar) {",
         "  const last = Number(pm.collectionVariables.get('lastTotpStep_' + secretVar) || 0);",
         "  const now = Date.now();",
         "  const wait = Math.floor(now / 30000) <= last ? (last + 1) * 30000 - now + 1500 : 0;",
         "  setTimeout(() => {",
         "    const t = Date.now();",
         "    pm.collectionVariables.set('totp', totp(pm.collectionVariables.get(secretVar), t));",
         "    pm.collectionVariables.set('lastTotpStep_' + secretVar, String(Math.floor(t / 30000)));",
         "  }, wait);",
         "}",
         "setTotp('staffTotpSecret');"
        ]
       }
      },
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "pm.collectionVariables.set('staffToken', pm.response.json().accessToken);"
        ]
       }
      }
     ]
    },
    {
     "name": "staffGetCustomer - Customer detail - profile, IP history, addresses, orders, policies, check-ins, security events, activity",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffGetCustomer"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{staffToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/customers/{{userId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "customers",
        "{{userId}}"
       ]
      },
      "description": "operationId `staffGetCustomer`. Customer detail - profile, IP history, addresses, orders, policies, check-ins, security events, activity"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "staffGetOrder - Order detail (recorded state; card token never shown)",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffGetOrder"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{staffToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/customers/{{userId}}/orders/{{orderId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "customers",
        "{{userId}}",
        "orders",
        "{{orderId}}"
       ]
      },
      "description": "operationId `staffGetOrder`. Order detail (recorded state; card token never shown)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "staffGetCase - Case detail, conversation (with email notification status), link candidates",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffGetCase"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{staffToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/cases/{{caseId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "cases",
        "{{caseId}}"
       ]
      },
      "description": "operationId `staffGetCase`. Case detail, conversation (with email notification status), link candidates"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));"
        ]
       }
      }
     ]
    },
    {
     "name": "staffLogout - Sign out (?all=true for every session)",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "staffLogout"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{staffToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/staff/logout",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "staff",
        "logout"
       ]
      },
      "description": "operationId `staffLogout`. Sign out (?all=true for every session)"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 204', () => pm.response.to.have.status(204));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "9. Sign out",
   "description": "",
   "item": [
    {
     "name": "logout - End this session (or ?all=true for every session). Access tokens stop working immediately.",
     "request": {
      "method": "POST",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "logout"
       },
       {
        "key": "Authorization",
        "value": "Bearer {{accessToken}}"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/auth/logout",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "auth",
        "logout"
       ]
      },
      "description": "operationId `logout`. End this session (or ?all=true for every session). Access tokens stop working immediately."
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 204', () => pm.response.to.have.status(204));"
        ]
       }
      }
     ]
    }
   ]
  },
  {
   "name": "10. Observability",
   "description": "Every request above carried X-Vd-Run: {{runId}} (set once by the collection pre-request script) and came back with X-Vd-Trace-Id. Read the run back, then one trace.",
   "item": [
    {
     "name": "getRun - Every request of a test run tagged with X-Vd-Run - per-route p50/p95/max, the actions in order, the outliers",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getRun"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/observe/runs/{{runId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "observe",
        "runs",
        "{{runId}}"
       ]
      },
      "description": "operationId `getRun`. Every request of a test run tagged with X-Vd-Run - per-route p50/p95/max, the actions in order, the outliers"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('status 200', () => pm.response.to.have.status(200));",
         "const r = pm.response.json();",
         "pm.test('every request of this run is there', () => pm.expect(r.steps).to.be.at.least(20));",
         "console.log('Run page: ' + r.links.page);",
         "console.log('Server p95 ' + r.serverMs.p95 + ' ms over ' + r.steps + ' requests; outliers: ' + r.outliers);"
        ]
       }
      }
     ]
    },
    {
     "name": "getTraces - the trace of the last request (202 while X-Ray is still receiving it)",
     "request": {
      "method": "GET",
      "header": [
       {
        "key": "X-Vd-Run",
        "value": "{{runId}}"
       },
       {
        "key": "X-Vd-Interaction",
        "value": "getTraces"
       }
      ],
      "url": {
       "raw": "{{baseUrl}}/v1/observe/traces/{{lastTraceId}}",
       "host": [
        "{{baseUrl}}"
       ],
       "path": [
        "v1",
        "observe",
        "traces",
        "{{lastTraceId}}"
       ]
      },
      "description": "operationId `getTraces`. One trace - or, comma-separated, every trace of one click - cleaned, with a breakdown and clues"
     },
     "event": [
      {
       "listen": "test",
       "script": {
        "type": "text/javascript",
        "exec": [
         "pm.test('200 (the trace) or 202 (pending: try again in 10 s)', () => pm.expect([200, 202]).to.include(pm.response.code));",
         "const t = pm.response.json();",
         "console.log('Trace page: ' + t.links.page);",
         "if (pm.response.code === 200) { console.log('Clues: ' + JSON.stringify(t.clues)); }"
        ]
       }
      }
     ]
    }
   ]
  }
 ],
 "event": [
  {
   "listen": "prerequest",
   "script": {
    "type": "text/javascript",
    "exec": [
     "if (!pm.collectionVariables.get('runId')) pm.collectionVariables.set('runId', 'pm-' + Date.now().toString(36));"
    ]
   }
  },
  {
   "listen": "test",
   "script": {
    "type": "text/javascript",
    "exec": [
     "const traceId = pm.response.headers.get('X-Vd-Trace-Id');",
     "if (traceId) pm.collectionVariables.set('lastTraceId', traceId);"
    ]
   }
  }
 ]
}