Choosing an API

Estimate Your Real Estate API Bill Before Importing a Property List

Turn a property list into a monthly lookup estimate: distinct homes, revisits, cache reuse and failures, then compare plan and prepaid costs with a small Python worksheet.

7 min read

Price the workload, not the list

Real estate API pricing pages quote a rate per call, per record or per month. None of those numbers is your bill until you know your workload: how many distinct properties you will look up, how often you will look each one up again, how many inputs will fail, and how many repeats a cache absorbs. A list that looks small can become a large monthly volume once it is refreshed every week.

This guide builds a small worksheet for that estimate. It uses PropertiesAPI's billing rules because they are the ones we can state precisely, but the same four workload questions apply to any API that bills per lookup. The pricing page remains the authoritative source for plans; the worksheet only applies those rates to your assumptions.

Know exactly what is billed

Zillow links and addresses cost the same: 1 credit per successful lookup. Repeat lookups of the same home within 24 hours come from our cache and are free. Failed lookups, including errors such as not_found or address_ambiguous, are not billed. In a batch, the response meta reports the credits actually charged, so you can reconcile your estimate with real usage after the first run.

Those three rules mean the billable count is smaller than the number of rows you send. Successful live lookups are billed; cache hits and failures are not. An estimate that multiplies rows by a rate will overstate the cost of a messy list and understate the effect of frequent refreshes.

Outcome of a lookupBilled?
Successful live lookup, by link, zpid or addressYes: one credit.
Same home again within the cache windowNo: served from the cache.
Error, such as no matching propertyNo.
Batch item that fails while others succeedNo, for the failed item only.

Write down four workload assumptions

Every input to the worksheet is an assumption you should be able to defend. Write each one down before you run anything, and revise them after a small pilot. The values in the script are placeholders that show the shape of the calculation, not a typical customer.

  • Distinct properties: the number of homes after removing duplicate rows. Two spellings of one address are one home once identity is confirmed.
  • Checks per month: how many times you look each home up. A one-off enrichment is one check; a weekly refresh is about four.
  • Success share: the fraction of lookups that return a property. Measure it on a sample of your own list rather than guessing.
  • Cache share: the fraction of checks that repeat a home inside the cache window. Most monthly or weekly refreshes have none; dashboards that reload the same homes several times a day have many.

The worksheet

The script below computes billable lookups from those assumptions, then the monthly cost of each option you list in plans.csv. It deliberately holds no prices of its own. Copy each plan's monthly price, included lookups and rate past them from the pricing page into the file, along with the prepaid rate if you are considering pay-as-you-go credits. Prices can change, and a worksheet that reads them from a file you refresh is easier to keep honest than one with numbers buried in code.

It is plain Python with Decimal arithmetic for money. It does not call the API, so you can run it as often as you like at no cost.

estimate_bill.py
"""Estimate monthly billable lookups and cost for a property list.

Usage: python estimate_bill.py plans.csv
WORKLOAD values are assumptions to replace. Fill plans.csv from the pricing page,
one row per option you are considering:
  name,monthly_usd,included_lookups,usd_per_1000_after
For prepaid credits, use monthly_usd 0, included_lookups 0 and the prepaid rate,
then round the result up to the pack sizes on sale.
"""
import csv
import math
import sys
from decimal import Decimal

WORKLOAD = {
    "properties": 2000,       # distinct homes after removing duplicate inputs
    "checks_per_month": 4,    # times you look up each home in a month
    "success_share": 0.95,    # lookups that return a property; failures are free
    "cache_share": 0.0,       # checks that repeat a home within 24 hours; cache hits are free
}


def billable_lookups(w):
    checks = w["properties"] * w["checks_per_month"]
    return math.ceil(checks * (1 - w["cache_share"]) * w["success_share"])


def monthly_cost(option, lookups):
    extra = max(0, lookups - int(option["included_lookups"]))
    rate = Decimal(option["usd_per_1000_after"])
    total = Decimal(option["monthly_usd"]) + Decimal(extra) / 1000 * rate
    return total.quantize(Decimal("0.01"))


def main(plans_path):
    lookups = billable_lookups(WORKLOAD)
    print(f"Billable lookups a month: {lookups:,}")
    with open(plans_path, newline="", encoding="utf-8") as f:
        options = list(csv.DictReader(f))
    for option in sorted(options, key=lambda o: monthly_cost(o, lookups)):
        cost = monthly_cost(option, lookups)
        print(f"{option['name']:<16} ${cost:>10,.2f}   ${cost / lookups * 1000:,.2f} per 1,000")


if __name__ == "__main__":
    main(sys.argv[1])

Fill in the plans file and read the result

Each paid plan includes its lookups. Past them, you don't hit a wall: each successful lookup costs your plan's rate per 1,000 lookups, shown on each plan. Prepaid credits are bought in packs, used after a plan's included lookups when you have both, and expire after the period shown on the pricing page. Enter prepaid credits as an option with no monthly price and no included lookups, then round the result up to the pack sizes on sale.

With the placeholder workload, the script reports the billable count and one line per option, cheapest first. Which option is cheapest depends entirely on the assumptions: change checks per month or the success share and the order changes. Treat the output as a range to plan around, not a quote.

The worksheet compares each option on its own. It does not model a plan combined with prepaid credits, and it treats prepaid credits as if they were used within the month. If your volume is irregular, compare a few months together rather than one.

plans.csv (copy the values from the pricing page) and a run
name,monthly_usd,included_lookups,usd_per_1000_after
Starter,<monthly price>,<included lookups>,<rate per 1,000 after them>
Pro,<monthly price>,<included lookups>,<rate per 1,000 after them>
Prepaid,0,0,<prepaid rate per 1,000>

$ python estimate_bill.py plans.csv
Billable lookups a month: 7,600
<one line per option in plans.csv, cheapest first: monthly cost and cost per 1,000>

Test the assumptions with a pilot before the import

Run a small, representative sample of the list through the batch endpoint, up to 50 rows per request. Count successful items, failed items and cache hits from the response meta, and compare meta.credits with your estimate for the same rows. If the success share is much lower than you assumed, fix the inputs before you import the rest: failures are free, but a list that mostly fails is not useful data.

If you plan to refresh the list on a schedule, also decide how old a collection may be before you look it up again. A shorter refresh interval multiplies the billable count; a longer one reduces cost but means the stored data is older. That trade-off is a product decision, not an API setting.

Keep spend bounded after launch

Paid plans can set an optional monthly spending limit, which is off unless you set one. When a limit is reached, further live lookups stop with an error instead of being billed, and cached homes still work. Setting a limit near your estimate is a simple guard against a runaway job.

Reconcile monthly. Compare the credits reported by the API with your estimate and adjust the assumptions, especially the success share and the number of revisits. After a couple of cycles the worksheet becomes a reliable budget rather than a guess.

Validation scope: with the pricing page's values entered in plans.csv, the script's plan results were checked against the site's own pricing calculation for several volumes. The workload values are placeholders, not measurements of any customer.

Sources and documentation

Product details checked against the linked documentation on October 22, 2026. Code and sample inputs are illustrative unless stated otherwise.

Written by PropertiesAPI, an independent property data service. We are not affiliated with Zillow. Source data may be incomplete or change over time.