{
  "info": {
    "_postman_id": "78cf61d6-4166-59fe-a8c9-486748a048b4",
    "name": "Monthly Payment API",
    "description": "Requests for the public Monthly Payment API (https://payment-estimate-api.pages.dev). Guide: https://payment-estimate-api.pages.dev/ — full spec: openapi.json.\n\nVariables: `baseUrl` (default https://payment-estimate-api.pages.dev) and `brand` (default `stoltzfus`, the only live brand).\n\nDisplay rules: always show a loan payment with its APR and term; keep the * and † markers; never show an APR for rent-to-own; name lenders only at the Apply step (`loans.terms[].offers`); point Apply buttons at `applyLink` and honor `applyOpensNewTab`; show every block of `disclosures.forThisWidget` in order; show `loans.notice` / `overMaxText` beside the payments.\n\nAPPLY LINKS ARE DELIBERATELY NOT INCLUDED. `GET /v1/{brand}/apply/{product}` (product = upgrade | thrive | rto, optional `?term=<months>`) answers 302 to the lender and counts one Apply click in production statistics on every request — so it must never be run from Postman, Newman, tests or monitors. It is documented in openapi.json (302 with a Location header; 404 `{ok:false, error:'unknown_product', products:[...]}` for an unknown product). Use it only as the href of a real Apply button, taken from `applyLink` in an estimate.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Discovery",
      "description": "Which brands are live.",
      "item": [
        {
          "id": "f0d78388-20c0-5d73-adaf-db5c25054c97",
          "name": "List live brands",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/brands",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands"
              ]
            },
            "description": "Brands whose estimator is live. Only `stoltzfus` today."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('stoltzfus is live', () => pm.expect(b.brands.map(x => x.brand)).to.include('stoltzfus'));"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Estimates",
      "description": "What a widget calls: /options once on page load, then /estimate with the customer's answers.",
      "item": [
        {
          "id": "c64faf0b-4c34-5811-ad48-ea7ba9a1b590",
          "name": "Get options (questions, lenders, disclosures)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "options"
              ]
            },
            "description": "Build the two dropdowns from `questions.creditScore.options` and `questions.ownProperty.options`; send each chosen `value` to /estimate."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('Has both questions', () => { pm.expect(b.questions.creditScore.options).to.be.an('array').that.is.not.empty; pm.expect(b.questions.ownProperty.options).to.be.an('array').that.is.not.empty; });",
                  "pm.test('Lenders upgrade, thrive, rto', () => pm.expect(b.lenders).to.have.all.keys('upgrade','thrive','rto'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "9d85fec9-3632-55f6-8dbd-85d705bfea9c",
          "name": "Estimate — POST (JSON body)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ]
            },
            "description": "The normal widget call. Returns one payment per term in `loans.terms` (no lender names), per-lender detail in `loans.byLender`, rent-to-own in `rentToOwn`, and the disclosures to show.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"zip\": \"19310\",\n  \"price\": 16820,\n  \"creditScore\": \"720\",\n  \"ownProperty\": \"own\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is true', () => pm.expect(b.ok).to.equal(true));",
                  "pm.test('Has loans, rentToOwn, disclosures', () => { pm.expect(b.loans).to.be.an('object'); pm.expect(b.rentToOwn).to.be.an('object'); pm.expect(b.disclosures.forThisWidget).to.be.an('array').that.is.not.empty; });",
                  "pm.test('Every loan payment carries an APR and a term', () => b.loans.terms.forEach(t => { pm.expect(t.aprDisplay).to.match(/APR/); pm.expect(t.label).to.be.a('string').and.not.empty; }));",
                  "pm.test('Apply links point at the API (click-counting), not the lender', () => b.loans.terms.forEach(t => t.offers.forEach(o => pm.expect(o.applyLink).to.include('/apply/'))));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "4b27b5fa-375a-56af-bba6-7ca141fa8ca1",
          "name": "Estimate — GET (query string)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                }
              ]
            },
            "description": "Same as POST, handy in a browser tab."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is true', () => pm.expect(b.ok).to.equal(true));",
                  "pm.test('Has loans, rentToOwn, disclosures', () => { pm.expect(b.loans).to.be.an('object'); pm.expect(b.rentToOwn).to.be.an('object'); pm.expect(b.disclosures.forThisWidget).to.be.an('array').that.is.not.empty; });",
                  "pm.test('Every loan payment carries an APR and a term', () => b.loans.terms.forEach(t => { pm.expect(t.aprDisplay).to.match(/APR/); pm.expect(t.label).to.be.a('string').and.not.empty; }));",
                  "pm.test('Apply links point at the API (click-counting), not the lender', () => b.loans.terms.forEach(t => t.offers.forEach(o => pm.expect(o.applyLink).to.include('/apply/'))));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "5a859b87-2175-51d5-969d-e4bfa8ffcee7",
          "name": "Estimate — over every maximum ($300,000)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ]
            },
            "description": "Price over the lender maximums: payments are for the maximum and `loans.notice` / `overMaxText` explain it — show those beside the payments.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"zip\": \"19310\",\n  \"price\": 300000,\n  \"creditScore\": \"840\",\n  \"ownProperty\": \"own\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is true', () => pm.expect(b.ok).to.equal(true));",
                  "pm.test('Has loans, rentToOwn, disclosures', () => { pm.expect(b.loans).to.be.an('object'); pm.expect(b.rentToOwn).to.be.an('object'); pm.expect(b.disclosures.forThisWidget).to.be.an('array').that.is.not.empty; });",
                  "pm.test('Every loan payment carries an APR and a term', () => b.loans.terms.forEach(t => { pm.expect(t.aprDisplay).to.match(/APR/); pm.expect(t.label).to.be.a('string').and.not.empty; }));",
                  "pm.test('Apply links point at the API (click-counting), not the lender', () => b.loans.terms.forEach(t => t.offers.forEach(o => pm.expect(o.applyLink).to.include('/apply/'))));",
                  "pm.test('loans.notice is set', () => pm.expect(b.loans.notice).to.be.a('string'));",
                  "pm.test('rentToOwn.overMaxText is set', () => pm.expect(b.rentToOwn.overMaxText).to.be.a('string'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "eb6041c6-1dc5-5984-bc0e-759deacf2df8",
          "name": "Estimate — no loan options (credit 500)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ]
            },
            "description": "No lender matches: `loans.available` is false — show `loans.message` instead of payments. Rent-to-own (no credit check) still shows.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"zip\": \"19310\",\n  \"price\": 16820,\n  \"creditScore\": \"500\",\n  \"ownProperty\": \"own\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('loans.available is false with a message', () => { pm.expect(b.loans.available).to.equal(false); pm.expect(b.loans.message).to.be.a('string'); });"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "b6338280-722f-5c88-bea3-ca0ff909ebeb",
          "name": "Estimate — rent-to-own not offered in the state (WI)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ]
            },
            "description": "Rent-to-own unavailable for the delivery state: show `rentToOwn.reasonText`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"zip\": \"53703\",\n  \"price\": 16820,\n  \"creditScore\": \"720\",\n  \"ownProperty\": \"own\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('rentToOwn unavailable, reason state', () => { pm.expect(b.rentToOwn.available).to.equal(false); pm.expect(b.rentToOwn.reason).to.equal('state'); });"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Simulate (test your error handling)",
      "description": "Add `simulate` (query string or JSON body) to /estimate to get a canned outcome. Simulated calls are not counted in usage statistics and carry an `X-Simulated` header. Never send `simulate` in production.",
      "item": [
        {
          "id": "f200e0cf-cebf-570a-aa98-06d29b5b94e9",
          "name": "simulate=unavailable → 503",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own&simulate=unavailable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                },
                {
                  "key": "simulate",
                  "value": "unavailable"
                }
              ]
            },
            "description": "What your widget sees when the API has switched itself off. Hide the payments or show a short note."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 503', () => pm.response.to.have.status(503));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is estimates_unavailable', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('estimates_unavailable'); });",
                  "pm.test('X-Simulated header is unavailable', () => pm.expect(pm.response.headers.get('X-Simulated')).to.equal('unavailable'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "ac69e388-fdcc-59bc-84ca-d064af5bb582",
          "name": "simulate=invalid → 400",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own&simulate=invalid",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                },
                {
                  "key": "simulate",
                  "value": "invalid"
                }
              ]
            },
            "description": "Validation error with customer-ready `fields` messages."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 400', () => pm.response.to.have.status(400));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is invalid_input', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('invalid_input'); });",
                  "pm.test('X-Simulated header is invalid', () => pm.expect(pm.response.headers.get('X-Simulated')).to.equal('invalid'));",
                  "pm.test('fields present', () => pm.expect(b.fields).to.be.an('object'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "ae9196e1-de05-5899-b60f-2d9aac252ce9",
          "name": "simulate=origin_blocked → 403",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own&simulate=origin_blocked",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                },
                {
                  "key": "simulate",
                  "value": "origin_blocked"
                }
              ]
            },
            "description": "What a browser on a non-allowed domain would get."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 403', () => pm.response.to.have.status(403));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is origin_not_allowed', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('origin_not_allowed'); });",
                  "pm.test('X-Simulated header is origin_blocked', () => pm.expect(pm.response.headers.get('X-Simulated')).to.equal('origin_blocked'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "a2c29009-56c0-57ba-88dd-1fe888b1fa98",
          "name": "simulate=rto_unavailable → 200",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own&simulate=rto_unavailable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                },
                {
                  "key": "simulate",
                  "value": "rto_unavailable"
                }
              ]
            },
            "description": "Normal body, but the live rent-to-own quote \"failed\": `rentToOwn.available=false`, `eligible=true`, `reason=quote_unavailable`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "pm.test('X-Simulated header is rto_unavailable', () => pm.expect(pm.response.headers.get('X-Simulated')).to.equal('rto_unavailable'));",
                  "const b = pm.response.json();",
                  "pm.test('rentToOwn quote unavailable but eligible', () => { pm.expect(b.rentToOwn.available).to.equal(false); pm.expect(b.rentToOwn.eligible).to.equal(true); pm.expect(b.rentToOwn.reason).to.equal('quote_unavailable'); });"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "d52f2695-7252-5195-837d-9b75f876835e",
          "name": "simulate=slow → 200 after ~4 s",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own&simulate=slow",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                },
                {
                  "key": "simulate",
                  "value": "slow"
                }
              ]
            },
            "description": "Normal body after about 4 seconds — check your loading state and that stale responses are ignored."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200', () => pm.response.to.have.status(200));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "pm.test('X-Simulated header is slow', () => pm.expect(pm.response.headers.get('X-Simulated')).to.equal('slow'));",
                  "pm.test('Took at least 3.5 s', () => pm.expect(pm.response.responseTime).to.be.at.least(3500));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "dca65534-6255-57f8-ba92-5ca25801b418",
          "name": "simulate=invalid via POST body → 400",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ]
            },
            "description": "`simulate` also works in the JSON body.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"zip\": \"19310\",\n  \"price\": 16820,\n  \"creditScore\": \"720\",\n  \"ownProperty\": \"own\",\n  \"simulate\": \"invalid\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 400', () => pm.response.to.have.status(400));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is invalid_input', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('invalid_input'); });",
                  "pm.test('X-Simulated header is invalid', () => pm.expect(pm.response.headers.get('X-Simulated')).to.equal('invalid'));"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Real errors",
      "description": "Real (not simulated) error responses.",
      "item": [
        {
          "id": "0747376c-edc2-55ca-ac2c-99f4aa979516",
          "name": "Invalid input → 400",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=123&price=abc&creditScore=720&ownProperty=own",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "123"
                },
                {
                  "key": "price",
                  "value": "abc"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                }
              ]
            },
            "description": "Bad ZIP and price. Show each `fields` message next to its input."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 400', () => pm.response.to.have.status(400));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is invalid_input', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('invalid_input'); });",
                  "pm.test('zip and price flagged', () => pm.expect(b.fields).to.have.all.keys('zip','price'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "a28797e8-f5ea-5f41-ab6e-19f085b10a73",
          "name": "ZIP 00000 → 400",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=00000&price=16820&creditScore=720&ownProperty=own",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "00000"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                }
              ]
            },
            "description": "ZIPs starting 000–004 are rejected."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 400', () => pm.response.to.have.status(400));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is invalid_input', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('invalid_input'); });",
                  "pm.test('zip flagged', () => pm.expect(b.fields).to.have.property('zip'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "1c2ca798-ecd2-5262-9e50-14ef29c625a9",
          "name": "Invalid JSON body → 400",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ]
            },
            "description": "Malformed JSON body.",
            "body": {
              "mode": "raw",
              "raw": "{\"zip\":\"19310\",",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 400', () => pm.response.to.have.status(400));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is invalid_json', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('invalid_json'); });"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "48999509-40a5-5b2b-b53d-c8a7d72421f1",
          "name": "Brand not enabled → 404",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/horizon/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "horizon",
                "options"
              ]
            },
            "description": "Brands other than stoltzfus are not live yet."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 404', () => pm.response.to.have.status(404));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is brand_not_enabled', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('brand_not_enabled'); });",
                  "pm.test('liveBrands listed', () => pm.expect(b.liveBrands).to.include('stoltzfus'));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "ab420823-9d0d-55bb-ba12-a004a70d66c0",
          "name": "Unknown route → 404",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/nope",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "nope"
              ]
            },
            "description": "Unknown route; the body lists the routes that exist."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 404', () => pm.response.to.have.status(404));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is not_found', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('not_found'); });",
                  "pm.test('routes listed', () => pm.expect(b.routes).to.be.an('array').that.is.not.empty);"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "2bae24c3-880a-5c17-9295-3b76164cdd31",
          "name": "Method not allowed → 405",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "options"
              ]
            },
            "description": "/options only supports GET."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 405', () => pm.response.to.have.status(405));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is method_not_allowed', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('method_not_allowed'); });"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "ceb70748-7e07-5809-87d9-3ad67d816f37",
          "name": "Origin not allowed → 403",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Origin",
                "value": "https://evil.example"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "estimate"
              ],
              "query": [
                {
                  "key": "zip",
                  "value": "19310"
                },
                {
                  "key": "price",
                  "value": "16820"
                },
                {
                  "key": "creditScore",
                  "value": "720"
                },
                {
                  "key": "ownProperty",
                  "value": "own"
                }
              ]
            },
            "description": "A browser Origin that is not on the allow-list (`https://*.mysheds.com`, `http://localhost:*`). Server calls without an Origin header are always allowed. (Postman desktop / Newman send the Origin header; the Postman web app may not.)"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 403', () => pm.response.to.have.status(403));",
                  "pm.test('Body is JSON', () => pm.response.to.be.json);",
                  "const b = pm.response.json();",
                  "pm.test('ok is false and error is origin_not_allowed', () => { pm.expect(b.ok).to.equal(false); pm.expect(b.error).to.equal('origin_not_allowed'); });"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Diagnostics",
      "description": "Health and the mirrored rules.",
      "item": [
        {
          "id": "f5156d7d-77f2-5800-a1d7-fada3d408573",
          "name": "Health",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "health"
              ]
            },
            "description": "200 `ok:true` while estimates are served; 503 when the API has switched itself off because the estimator's calculation logic changed."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200 or 503', () => pm.expect(pm.response.code).to.be.oneOf([200, 503]));",
                  "const b = pm.response.json();",
                  "pm.test('ok matches status', () => pm.expect(b.ok).to.equal(pm.response.code === 200));"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "id": "6bd282de-6d06-5577-9e10-ffaaa4329ddf",
          "name": "Rules snapshot",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/{{brand}}/rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "{{brand}}",
                "rules"
              ]
            },
            "description": "Normalized snapshot of the rules mirrored from the estimator (diagnostics only — use /estimate for payments)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status is 200 or 503', () => pm.expect(pm.response.code).to.be.oneOf([200, 503]));",
                  "const b = pm.response.json();",
                  "pm.test('Has a snapshot with lenders', () => pm.expect(b.snapshot.lenders).to.have.all.keys('upgrade','thrive','rto'));"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    }
  ],
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "pm.test('Responses are never cached', () => pm.expect(pm.response.headers.get('Cache-Control') || '').to.include('no-store'));"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://payment-estimate-api.pages.dev",
      "type": "string"
    },
    {
      "key": "brand",
      "value": "stoltzfus",
      "type": "string"
    }
  ]
}