An open, keyless API and MCP server for checking whether a California home qualifies for a Proposition 8 decline-in-value property tax reduction, and for starting a filing the owner completes themselves. The check is free, runs the exact pipeline our website runs, and can only ever lower a tax bill, never raise it. Filing is a flat $29, paid by the owner.
qualifies with a continueTokenPOSThttps://saveproptax.com/api/agent/check · keyless · optional x-agent-key header
curl -s https://saveproptax.com/api/agent/check \
-H 'Content-Type: application/json' \
-d '{"address": "123 Main St, Walnut Creek"}'
Add "unit": "Unit 12" for multi-unit buildings. A qualifying response:
{
"status": "qualifies",
"message": "This property qualifies: recent comparable sales support a value of
$915,000 against the $1,024,000 assessment, an estimated $1,204 per year in
tax savings; filing costs a flat $29 and the owner signs and pays.",
"property": { "address": "123 MAIN ST, WALNUT CREEK CA", "county": "Contra Costa" },
"estimate": { "assessment": 1024000, "opinion": 915000, "estimatedAnnualSavings": 1204 },
"filing": { "feeUsd": 29, "deadline": "November 30, 2026" },
"continueToken": "eyJ...signed, valid 24 hours",
"cached": false,
"checkedAt": "2026-08-19T18:00:00.000Z"
}
estimate appears when a valuation was computed (qualifies and fair_assessment). filing appears when filing terms are relevant (qualifies and window_closed). continueToken appears only on qualifies. message is always a one-sentence explanation you can relay to your user as is.
| status | meaning |
|---|---|
| qualifies | Comparable sales support a defensibly lower value, estimated savings exceed $500 a year, and the county's window is open. The only status that carries a continueToken. |
| fair_assessment | Comparable sales do not support a value below the current assessment this year. Includes the estimate so the owner can see why. |
| not_enough_data | Too few closely matched recent sales for a defensible filing. We decline rather than file on weak evidence. |
| not_residential | The parcel's county record is not residential. Our method is comparable home sales, so we only serve homes. |
| window_closed | The county's review window has closed for this year. filing.reopens says when it returns. |
| county_not_served | The property is in a California county we do not serve yet. |
| already_filed | A review request was already filed for this property this year. No document identifiers are returned, to anyone. |
| address_not_found | The address could not be located. Retry with the city included, for example "123 Main St, Walnut Creek". |
| needs_more_info | More input is needed; the message says what, for example a unit number for a multi-unit building. |
| busy | Heavy load: fresh checks are briefly paused, cached answers still serve. Retry shortly. |
POSThttps://saveproptax.com/api/agent/prepare
curl -s https://saveproptax.com/api/agent/prepare \
-H 'Content-Type: application/json' \
-d '{
"continueToken": "eyJ...from a qualifying check",
"ownerName": "Jane Homeowner",
"ownerPhone": "925 555 0100",
"ownerEmail": "jane@example.com"
}'
{
"docId": "d0c1d2...",
"status": "awaiting_signature",
"statusUrl": "https://saveproptax.com/api/agent/status?doc=d0c1d2...",
"ownerEmailSent": true
}
We fill the county's own review form, attach the comparables worksheet, create the e-sign document, and email the signing link to ownerEmail. The response never contains the signing link. The owner name goes on the county form exactly as sent, so it should match title. Phone numbers need an area code. Limited to 3 prepares per owner email per day; the token must come from a check less than 24 hours old, and the window and one-filing-per-year guard are re-checked at prepare time.
Errors return { "error": "relayable message" } with 400 (bad input), 401 (bad or expired token), 409 (already filed, or the evidence refreshed and the numbers changed: run a fresh check), 422 (window closed), or 429 (owner email at its daily link limit).
GEThttps://saveproptax.com/api/agent/status?doc=<docId>
curl -s 'https://saveproptax.com/api/agent/status?doc=d0c1d2...'
{ "status": "filed", "updatedAt": "2026-08-19T18:30:00.000Z",
"message": "Payment is complete and the filing is on its way to the County Assessor." }
Coarse states only: awaiting_signature → awaiting_payment → filed → delivered. No personal information, no addresses, no amounts, ever. Unknown or invalid ids return { "status": "unknown" }.
POSThttps://saveproptax.com/api/agent/register · instant, no approval step
curl -s https://saveproptax.com/api/agent/register \
-H 'Content-Type: application/json' \
-d '{"agentName": "MyAssistant", "contactEmail": "dev@example.com"}'
Returns a key immediately. Send it as the x-agent-key header on check and prepare calls. A key buys double the fresh-check headroom under the hourly budget and named attribution in our funnel (useful once you want to talk partnership: your traffic has a name). Anonymous use works fine for trying it out; registration is identity, not permission.
A remote MCP server over streamable HTTP, no auth, stateless:
https://saveproptax.com/mcp
Tools, mirroring the endpoints above:
| tool | arguments |
|---|---|
check_property_tax_savings | address (required), unit |
start_filing | continueToken, ownerName, ownerPhone, ownerEmail (all required) |
get_filing_status | docId (required) |
Same rules as the HTTP API: start_filing emails the owner and never returns the signing link in the tool result.
cached: true. Cached answers are never rate limited.busy while cached answers still serve; the pipeline never degrades to a cheaper method, it only delays. The number an agent sees is the number the website shows is the number that gets filed.qualifies when estimated savings exceed $500 a year; below that, a filing is not worth the owner's effort and we say so.Building a homeowner product, an assistant with real users, or a brokerage tool? We are open to embeds, volume arrangements, and co-branding. Write to support@saveproptax.com with what you are building; a human founder replies.