For AI agent builders

Zillow API for AI agents

Give the agent you are building a property lookup tool. Define it with JSON Schema, run it against the REST API from your own code, and return results and errors in a form the model can reason about.

The job

If you use Claude, Cursor or another MCP client, connect our MCP server and you are done. But if you are building your own agent, with your own loop and your own framework, you need a tool definition the model can call and a handler that runs it safely.

The handler is where the work is: the model will send addresses without a ZIP, ask for the same home three times, and keep going after an error unless the error tells it what to do. The example below handles each of those in a few lines.

The workflow

  1. Define the tool. One tool with url, zpid and address as optional string properties and a description that tells the model to prefer links and to give exactly one input.
  2. Run it in your code. Your handler validates the arguments, calls GET /v1/zillow/property with your key, and returns the JSON. The key never goes to the model.
  3. Return errors as instructions. Turn address_ambiguous into 'the closest match is this link; ask the user or look it up by url', and not_found into 'ask for the Zillow link'. Models recover well from errors that say what to do next.
  4. Guard spend. Read x-credits-remaining on every response and stop the loop below a floor you choose. Repeats within the cache window are free, so tell the model to look a home up again rather than guess.

Tool definition and handler

Framework-neutral JavaScript. The schema works with any model API that takes JSON Schema tools; plug the handler into your agent loop.

JavaScript (Node.js)
export const zillowTool = {
  name: "get_zillow_property",
  description:
    "Look up one home's Zillow listing and property data. Give exactly one of url (a zillow.com link, preferred), " +
    "zpid, or address (full US address with city and state or ZIP). Null fields mean Zillow doesn't show them.",
  input_schema: {
    type: "object",
    properties: {
      url: { type: "string", description: "A zillow.com/homedetails link" },
      zpid: { type: "string", description: "Zillow property id (digits)" },
      address: { type: "string", description: "Full address, e.g. 2640 Steiner St, San Francisco, CA 94115" },
    },
  },
};

const MIN_CREDITS = 20; // stop the agent before the account runs dry

export async function runZillowTool(args) {
  const given = ["url", "zpid", "address"].filter((k) => typeof args?.[k] === "string" && args[k].trim());
  if (given.length !== 1) return { error: "Give exactly one of url, zpid or address." };

  const url = new URL("https://api.propertiesapi.com/v1/zillow/property");
  url.searchParams.set(given[0], args[given[0]].trim());
  const res = await fetch(url, { headers: { "x-api-key": process.env.PROPERTIESAPI_KEY }, signal: AbortSignal.timeout(120_000) });
  const left = Number(res.headers.get("x-credits-remaining"));
  const body = await res.json();

  if (res.ok) {
    const { data, meta } = body;
    return { data, cached: meta.cached, ...(left < MIN_CREDITS ? { warning: "Credits are low: finish this task without more lookups." } : {}) };
  }
  const e = body.error;
  switch (e.code) {
    case "address_ambiguous":
      return { error: `Zillow's closest match is a different home: ${e.details?.candidate?.detailUrl}. Ask the user if that is the home, then call again with url.` };
    case "not_found":
      return { error: "No Zillow property matches. Ask the user for the Zillow link or a fuller address." };
    case "rate_limited":
    case "upstream_busy":
      return { error: `Busy. Wait ${e.details?.retry_after ?? 30} seconds before trying again.` };
    default:
      return { error: `${e.code}: ${e.message}` };
  }
}

Fields that matter here

Response fields for ai agent builders
FieldWhy it matters
data (whole object)Return it as-is; models handle the field names well. Strip photos and priceHistory if you need to save context.
meta.cachedLets the agent tell the user whether data was read just now or earlier in the day.
error.details.candidate.detailUrlThe closest match for an ambiguous address: the agent can confirm it with the user and retry by link.
x-credits-remaining headerYour spend guard. The model never sees the key or the header; your handler does.
calculatedArithmetic the model would otherwise do itself, and sometimes get wrong, such as yields and price changes.

What it won't do

  • One home per call, or up to 50 items per batch request. The agent can't ask for all homes in an area.
  • The model sees what Zillow shows. It shouldn't present a Zestimate as an appraisal or our own estimate; there is none.
  • No tenant, credit, insurance or employment decisions covered by the FCRA.
  • Lookups take seconds, so stream a status message while the tool runs.

AI agent builders: questions

Should I use MCP or function calling?+

Use the MCP server when your agent runs in an MCP client such as Claude or Cursor. Use function calling against the REST API when you own the agent loop. Both bill the same way.

Which models does this work with?+

Any model that supports tool or function calling with JSON Schema. The tool definition above is plain JSON Schema.

How do I stop an agent from spending too much?+

Check x-credits-remaining in your handler, as the example does, and cap tool calls per task. Repeat lookups of the same home within 24 hours come from our cache and are free.

What does a tool call cost?+

Zillow links and addresses cost the same: 1 credit per successful lookup. Failed calls cost nothing.

Try it on your own list

50 free lookups, no card, no approval.

Zillow is a trademark of Zillow, Inc. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow.