How it works
- On page load, call
GET /v1/{brand}/optionsto get the two customer questions (labels + dropdown values). - With the customer’s answers plus the structure’s delivery ZIP and total price, call
POST /v1/{brand}/estimate. - Show what comes back. Eligibility, terms, payments, APRs, Apply links and disclosure text are all pre-computed.
https://payment-estimate-api.pages.dev · Brand: stoltzfus (more brands coming)JSON in, JSON out, no API key. Browser calls are allowed from
mysheds.com (any subdomain) and localhost. Calls from your server (no Origin header) are always allowed. Need another domain allowed, e.g. a staging site? Ask Tim Brazil.
shedlender.com/estimator.html), so your widget can never disagree with it. If our estimator’s calculation logic changes, the API returns HTTP 503 estimates_unavailable until we confirm the change. Please handle that by hiding the payment section or showing a short “unavailable” note.
Your widget, your layout
You decide when to call the API and how to show the results. No button is required. The test page lets you try three styles:
- A · Automatic: call
/estimatewhenever an input or the price changes, and show the payments without a button. - B · Term dropdown: show one payment and let the customer switch terms from a dropdown (start on
loans.defaultTermYears). - C · Button + tiles: the customer presses a button, then picks a term tile.
For automatic updates, only call the API once the ZIP has 5 digits, price is above 0 and both questions are answered. Wait about 0.4 seconds after the last keystroke, and ignore any older response that comes back after a newer one. That keeps it fast and well under the limit of 120 rent-to-own quotes per visitor every 10 minutes. Switching terms needs no new call, because every term is already in the response.
1 · GET /v1/stoltzfus/options
Build your two <select>s from questions.creditScore.options and questions.ownProperty.options, then send the value back.
{
"questions": {
"creditScore": { "label": "What is your credit score?", "placeholder": "— Select credit score —",
"options": [ { "value": "840", "label": "840 – 850" }, … { "value": "500", "label": "Below 550" } ] },
"ownProperty": { "label": "Do you own the property where the project is located?",
"options": [ { "value": "own", "label": "Yes, I own the property" },
{ "value": "biz", "label": "Yes, my business owns the property" },
{ "value": "no", "label": "No, I do not own the property" } ] }
},
"lenders": { "upgrade": { "minCreditScore": 680, "minAmount": 2000, "maxAmount": 100000, … }, "thrive": { … }, "rto": { … } },
"disclosures": { … }, "displayRules": [ … ]
}
2 · POST /v1/stoltzfus/estimate
| Field | Type | Example | Notes |
|---|---|---|---|
zip | string | "19310" | Delivery ZIP. Sets sales tax and state availability for rent-to-own. |
price | number | 16820 | Total price of the structure with options, in dollars. |
creditScore | string | "720" | A value from /options. |
ownProperty | string | "own" | A value from /options (own · biz · no). |
GET /v1/stoltzfus/estimate?zip=19310&price=16820&creditScore=720&ownProperty=own also works, which is handy for testing in a browser tab.
const res = await fetch('https://payment-estimate-api.pages.dev/v1/stoltzfus/estimate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ zip: '19310', price: 16820, creditScore: '720', ownProperty: 'own' })
});
if (res.status === 503) { /* hide the payment section — estimates temporarily off */ }
const data = await res.json();
Response (shortened)
{
"ok": true,
"input": { "zip": "19310", "state": "PA", "price": 16820, "creditScore": "720", "ownProperty": "own" },
"loans": {
"available": true,
"defaultTermYears": 20, // pre-select this term (the longest)
"terms": [ // ONE payment per term — use these for your payment choices
{ "years": 5, "label": "5-Year", "monthlyPaymentDisplay": "$372", "aprDisplay": "7.99% APR*", "footnote": "†",
"paymentNote": "Months 1–6 are interest-only; …", "bestLender": "thrive",
"offers": [ { "lenderName": "Thrive Financial", "monthlyPaymentDisplay": "$372", "aprDisplay": "7.99% APR*", "applyUrl": "…" },
{ "lenderName": "Upgrade", "monthlyPaymentDisplay": "$349", "aprDisplay": "8.99% APR*", "applyUrl": "…" } ] },
…
{ "years": 20, "label": "20-Year", "monthlyPaymentDisplay": "$164", "aprDisplay": "9.99% APR*", "footnote": "†", "offers": [ … Thrive only … ] }
],
"byLender": { // full per-lender detail, exactly as the estimator shows it
"upgrade": { "eligible": true, "terms": [ 5, 7, 10, 12, 15-year … ] },
"thrive": { "eligible": true, "terms": [ … incl. interestOnlyPayments for months 1–6 … ] }
},
"message": null // text to show when available = false
},
"rentToOwn": {
"available": true, "creditCheck": false, "defaultTermMonths": 48,
"terms": [ { "months": 24, "monthlyPaymentDisplay": "$1,048.77", "upfrontPayment": 3364 }, … ],
"overMaxText": null, // set when the price is over $25,000 (payments are for $25,000)
"notes": [ "Rent-to-own is not available for 2-story structures or …" ],
"applyUrl": "https://www.smartpayrentals.com/app/StoltzfusStructures",
"reason": null, "reasonText": null // why it’s unavailable, when available = false
},
"disclosures": {
"forThisWidget": [ // show ALL of these, in order — word-for-word from our estimator
{ "type": "paragraph", "paragraphs": [ { "text": "Stoltzfus Structures is not a lender…", "html": "…" } ] },
{ "type": "heading", "text": "Important Disclosures" },
{ "type": "section", "id": "disc-rto", "title": "Rent-to-Own (SmartPay)",
"paragraphs": [ { "text": "You do not own the structure until…", "html": "… higher than the cash price …" } ] },
…
],
"all": [ … ], // the estimator's whole footer, incl. products this widget doesn't offer
"representativeExample": "…", "rentToOwnStructures": "Structures that are 2 stories or … do not qualify for rent-to-own."
},
"displayRules": [ … ]
}
Errors return HTTP 400 with {"ok":false,"error":"invalid_input","fields":{"zip":"Enter a 5-digit US ZIP code"}}. The messages are written for customers, so you can show them as-is.
Display rules (please follow)
- Always show a loan payment together with its APR and term. Never show a payment by itself.
- Keep the * on APRs and the † on interest-only terms, and put the matching disclosures on the page.
- Don’t name the lender on the payment choices. Show lender names only at the Apply step (
terms[].offers). - No APR or interest rate for rent-to-own. It’s a lease, not a loan.
- When a result has
overMaxText, show it next to those payments. - Disclosures: render every block in
disclosures.forThisWidget, in order. Usehtmlto keep the bold text. They are copied word-for-word from our estimator and update automatically when we change them, so please don’t retype or edit them. - Show each lender’s
applyHint(the small print under our Apply buttons) next to its Apply button. - Don’t call any of these products “unsecured”.
Questions
Tim Brazil, Lending Manager. Rates and rules are maintained on our side. When a lender changes terms, our estimator is updated, and this API and your widget pick up the change automatically.