/v1 contract change. Once refreshes run on a
schedule, it has one entry per data refresh.
/v1 is additive only. Investorlift aims to give 30 days’ email notice before it removes or renames a documented route,
field or tool. It aims to give 90 days’ notice before it retires a paid plan (Developer Agreement Section 12.3). This log
records a removal on the day it ships.
Each change is one bullet. The bullet starts with a prefix that says the kind of change. It links the endpoint or the
page it touched, when it has one. A plumbing change, a setting or a metric often has none. The prefixes:
- Changed API 0.33.1: the
buildkind and its counters describe the six bounds of the pipeline rule, and nothing on the wire changes yet. The served rows of every market still follow the 0.13.0 rule, every lot-to-build resale by a registered investor, until that market’s next data build. From its first build after 2026-09-17 a market keeps only a single-family parcel held over 90 days, its house complete by the sale year. On the stage tables of 2026-09-14 that keeps 5,044 of Phoenix’s 10,006 builds and 5,747 of Houston’s 10,030, and the rest readotheragain. Everybuilddescription names the rule a market follows (meta.coverage[].loaded_at),n_lot_to_build_builtis described againstn_builds, anddocs/god-mode-api/PLAN-build-kind.mdsection 1 defines the bounds. Deal - Changed API 0.33.0: a
POST /v1/properties/searchwith aziplist and nopoint,bboxorproperty_idchooses its markets from the ZIP codes. The API searches only the markets whose parcels carry them, so the scan names those markets and walks their index alone. A ZIP no market carries is422 outside_coverage, and the problem names it inzips_unknown. Before this release the same request answered an empty page, or timed out for a Seattle or Houston ZIP under the default sort. Beside a geometry the list stays a filter, as documented. The MCP toolgodmode_property_searchfollows the route. - Changed The permit-window rule applies in every parcel market, where until 2026-09-17 it applied to a Washington market alone. Where
a jurisdiction issued no permit in the 12 months before the permit snapshot, its parcels carry the five windowed permit aggregates as
null. Then
meta.coverage[].parcel.permits.jurisdictions[].windows_measuredis false for it. The next Phoenix build marks Scottsdale, Goodyear and Gilbert. The field descriptions say so, and no field, filter or error code changes. - Added API 0.32.0: the lender family’s investor stubs carry the name to print under the rule of 0.29.0.
LenderInvestorRefgainsdisplay_name. That stub sits behindtop_borrowers[].investorandtop_investors[].investoron a lender profile (GET /v1/lenders/{lender_id}) and behindinvestoron a borrower row (GET /v1/lenders/{lender_id}/borrowers). A loan row’sdealblock gainsinvestor_display_namebesideinvestor_name(GET /v1/lenders/{lender_id}/loans), null exactly wheninvestor_nameis null. The profile computes the stubs’ names on read from the ids its detail carries. The MCP loans tool’s concise row drops the field withinvestor_name, and the borrowers and lender tools carry it in both formats. - Data Coverage recorded from production on 2026-09-17: hou dataset_version 1789490988 -> 1789629654; hou deeds: served (2026-08-11) -> served (2026-08-25); hou listings: served (2026-08-31) -> served (2026-09-07); hou addresses: not_published -> served (2026-08-25); phx dataset_version 1789533180 -> 1789636470; phx addresses: not_published -> served (2026-08-27); sea (WA: King, Pierce, Snohomish) is served, deeds to 2026-08-26. The Coverage pages and coverage.json are regenerated from the recording. No contract change.
- Data The Seattle metro (King, Pierce and Snohomish counties, WA) is served on production since 2026-09-17, dataset version 1789654598. It moves from the planned list to the served markets in
api/src/coverage/intent.tswith the stagebeta. The planned list is empty. No contract change. - Added API 0.31.0: the four investor stubs that carried
namealone carry the name to print too, under the rule of 0.29.0 (given-first for a cleanly parsed person, equal to the deed spelling otherwise, null exactly when the name is null):buyer.display_nameandintermediate.display_nameon an Investorlift wholesale listing (GET /v1/wholesale/listings),top_buyers[].display_nameon a wholesaler (GET /v1/wholesalers/{wholesaler_id}),investor_links[].investor_display_nameon an agent (GET /v1/agents/{agent_id}) andholder.display_nameon an agent’s listing row (GET /v1/agents/{agent_id}/listings). An unregistered company that bought a wholesale listing prints its name. The MCP tools carry the buyer’s, the top buyers’, the holder’s and the link’s field in both formats (the intermediate stays detailed-only, as before); the wholesale listings tool’s twenty-row concise budget widened from 20 KiB to 22 KiB (maximal rows measured 21,584 bytes). - Docs The Developer Agreement (Beta) is version
data-services-beta-v2026-09-17and the Privacy Notice is reworded with it: where both said a registry investor’s “display name”, they say its registry name, the deed spelling, and the same name printed given-first, so the legal texts and the API’sdisplay_namefield do not share a term for two different things. No right or obligation changes; the revision is a new agreement version under Section 21.1, published here and in the console once the console’s agreement id is repointed.
AddedChangedDataDocs
API 0.31.1, 0.30.0, 0.20.0, 0.29.0, 0.28.1, 0.28.0, 0.27.1, 0.27.0, 0.26.0, 0.25.0, 0.24.2, 0.24.1, 0.24.0, 0.23.0, 0.22.3, 0.22.2, 0.22.1, 0.22.0, 0.21.0, 0.19.0
- Docs API 0.31.1: every text behind the generated reference now reads as ASD-STE100 Simplified Technical English. That is the description of every field and parameter, the 38 operation descriptions, the response and header descriptions, the error catalogue (each code’s title, description and recovery sentence), the MCP tool descriptions, server instructions, prompts and resources, and the introduction of the OpenAPI document. The recovery sentence of every error body reads the same way, so a client shows the same words the catalogue carries. No field, code, status, limit, default or example changed. The rules that write the public document follow the new sentences. The preamble of this changelog reads the same way; the dated entries are a record and keep their text.
- Docs The documentation site now reads as ASD-STE100 Simplified Technical English (the DX-07 decision): every
hand-written page of the Guides, MCP and API reference tabs. A sentence has at most 25 words, or 20 in a procedure.
The voice is active, the words come from the approved list, and one term names one thing. No fact, field, code, limit
or example changed. The three legal pages and the dated entries of this changelog are records and keep their text.
The section heading “Reading the response” is now “Read the response” on every endpoint page, and the early-access
note reads the same on every page that carries it.
scripts/check-ste.mjsholds the pages to the rules as a step ofpnpm verifyand of CI;docs/god-mode-api/DOCS-STE.mdis the rule sheet. - Added API 0.30.0:
GET /v1/coverageanswers which data is available where. For every loaded market it returns one row per dataset a market can lack. The dataset keys aredeeds,listings,agents,wholesale,auction,str,parcels,financing,permits,owner_profile,history,cash_sale,lendersandaddresses. Each row carries onestatus:served,partial,not_measuredornot_published. Beside it are thedatedandstaleflags, theas_ofdate, and thegatea call meets where the API does not serve the dataset. The row also carries thegranularityand thescopeat that grain, and the market row carrieslabelandstage(Houston readsbeta). - Added API 0.30.0, the scope of the same route. The scope lists every county with its deed end and every short-term rental jurisdiction with its reason. It also lists every permit issuer and every history ZIP. Each county also says if the lender registry covers it.
- Added API 0.30.0, the place lookup of the same route. The place inputs are
latwithlng,county(a FIPS or a name),zip(through the Census ZIP-to-county crosswalk) andmarket. With one of them the answer is the markets that cover the place, with aplace_statusper dataset. A place nothing covers answers200withmarkets: []andplace.statusoutside, never422.planned[]lists the markets Investorlift intends to serve, as labelled intent (label_kind: intent, the Seattle metro, building). - Added API 0.30.0, the price and the MCP half: the route costs zero credits and takes any key. It is a probe, so behind
the gateway lock it needs no
X-On-Behalf-Of, likeGET /v1/dataset. Over/mcp,godmode_coverageruns the same plan, with the place inputs (latitude,longitude,county,zip,market) andresponse_format. Its rows carrylabel,stage,datasets(the matrix),str_citiesandhistory_zipsbeside the fields they always carried.data.placeanddata.plannedride besidedata.markets, and the concise format caps the jurisdiction, issuer and ZIP lists at ten.godmode://coverageandgodmode://markets/{market}serve the same rows, and the property-history tool now nameshistory_zips. - Changed API 0.30.0:
meta.coverage[].lendersgainsdatedandn_parcels_uncovered. No description of the public document or the public manifest names two loaded markets or two states any more, and a test holds it. The tools list ratchet moved once, to 188,416 bytes. - Docs The developer site gains a Coverage section, generated from a recording of production.
npm run coverage:recordwritesapi/coverage.snapshot.jsonandnpm run docs:syncrenders it. Which data is available where carries the markets table and the matrix. The matrix gives every as-of date with the snapshot and the stale marks. The legend and the planned markets follow, under the intent sentence. Counties, cities and ZIP codes lists every county, short-term rental jurisdiction, history ZIP and permit issuer. - Docs
coverage.jsonis the machine twin of those pages. The first recording is production on 2026-09-17 (API 0.20.0 there). It shows agents not published and the address resolve not yet released on production. The hand-written pages stop typing coverage facts. The market sentence on the introduction and the coverage guide is a generated region filled from the recording. Every hand-typed as-of date, covered county, history ZIP and served city became a pointer tometa.coverage[]or the Coverage pages. - Docs A test fails the next coverage fact typed by hand. The coverage guide no longer describes
GET /healthas reachable from the public host. - Added API 0.29.0:
display_nameon every investor object, the name to print besidename, which is the deed spelling: the investor summary of every deal row (GET /v1/deals,GET /v1/deals/{deal_id}, an investor’s deals, a parcel’s deals), the list row and the match row (GET /v1/investors,GET /v1/buyers/match), the profile (GET /v1/investors/{investor_id}), the search hit (GET /v1/investors/search) and a deal’ssellerandend_buyer. For a person whose deed spelling parsed cleanly it is GIVEN [MIDDLE] SURNAME [SUFFIX] in upper case (“DANA RIVERA” from the deed’s “RIVERA DANA”); for an entity, a trust, a public body, an institutional investor or an ambiguous spelling it equalsname; it is served wherevernameis and is null exactly whennameis null. It is assembled when the row is read from the member the registry already parsed, so no data refresh was needed and every market carries it at once.namekeeps its value and is now described as the deed spelling, SURNAME GIVEN for a person: match, join and search onname, printdisplay_name. The MCP tools carry the field in both formats (Tools); the twenty-row concise budget the deals tool is tested against was widened from 20 KiB to 21 KiB to keep it there. - Changed API 0.28.1: behind the gateway lock a request to a probe route (
GET /v1/dataset, the freshness probe) is served withoutX-On-Behalf-Of; every other/v1path and/mcpstill answer 400on_behalf_of_requiredwithout it. A probe charges nothing and counts against no credit line, so it needs no developer identity, and the gateway’s subscription-less forward of the probe (pricing plan) is served instead of refused (issue #26). - Added API 0.28.0:
meta.coverage[].parcel.permits.jurisdictions[].windows_measuredsays whether a jurisdiction’s permit feed is current. It is false when the jurisdiction issued no permit in the 12 months before the permit snapshot, so the parcels there carry null 24-month and 36-month permit windows (n_permits_24m,n_open_permits_12m,major_work_36m,tags_24m,job_value_24m: a stale feed, not a permit-free parcel); true where it did; null where the market does not record the flag (every Arizona and Texas jurisdiction today). The five window fields name that second reason in their null clauses. The negative permit filters ofPOST /v1/properties/search(filters.permits.open = false,filters.permits.major_work_36m = false) leave an unmeasured parcel out instead of answering it as permit-free; a parcel with no permit at all is answered as before, and the Phoenix and Houston results are unchanged (every jurisdiction there is measured). In the Seattle metro ten King County city feeds are unmeasured today (Redmond, Kirkland, Renton, Sammamish, Mercer Island, Auburn, Issaquah, Kenmore, Bothell, SeaTac; the vendor crawled them once around 2025-04-09). - Changed In Washington, a short-term rental parcel’s
business_usefollows the operator’s own declaration on the licence (the City of Seattle roll records whether the unit is the operator’s primary residence); the owner-occupied inference decides it everywhere else, and where the roll carries no declaration. Phoenix and Houston values are unchanged. The field’s description says so; no shape change. - Data The Seattle metro is the third market,
sea, served on stage: King 53033, Pierce 53053 and Snohomish 53061 (Washington, the Seattle-Tacoma-Bellevue MSA) as onemetromarket with the default 20-mile point tolerance, 1,321,876 parcels,data_end2026-08-26 (King 2026-08-25, Pierce 2026-08-26, Snohomish 2026-08-25), registry run 2,dataset_version1789595959 after the last swap of the day (1789572701 when the registry half was served; every sidecar swap restamps it): 198,176 deals (15,185 flips), 51,803 investors, 554,680 listings, the buyer signal, 2,074 Investorlift wholesale listings from 183 wholesalers, and the auction channel under Washington’s rules (RCW 61.24 trustee’s sales without redemption; sheriff’s deeds in two kinds; the treasurer’sPublic Actiondeed asTAX_SALE; a hard-money funder standing beside the bidder no longer hides the bidder’s purchase). Washington is a disclosure state, so every pair carries both prices and no deal isSELL_ONLY. The short-term rental block is served for the City of Seattle (the FAS roll s7df-xba4, 5,140 active units, matched at 92.7 percent with 99.64 percent of exact matches within 100 m; 8,363 parcels carry a licensed unit and the block is explained on all 224,605 Seattle parcels) andmeta.coverage[].strexplains all 81 Washington jurisdictions (every incorporated place of the three counties is a Census TIGER polygon). The parcel family is served from the BatchData archive: the parcel layer on all 1,321,876 parcels (a listing status on 86.5 percent, a last sale price on 82.3 percent, an AVM on 92.3 percent; the recorders’ nominal 313 / $370 served null), permits 4,309,331 on 689,890 parcels from the 2026-09-01 FULL (the vendor’s King labels corrected; the 24-month windows of a jurisdiction with no permit in the 12 months before the snapshot served null), financing on 1,318,098 parcels (887,892 open liens, dated 2026-06-25), owner profiles on 1,307,411 (as of 2026-09-09), the history change log for ZIP 98133 (165,126 events over 62 weeks, 862 listing cycles), and the lender registry (21,302 served lenders of 57,175 identities, 3,740,525 loan rows, 4,142,060 borrower rows; the same id for the same lender in every market), and the street-address resolve (1,309,532 of 1,321,876 parcels parsed, 99.1 percent). Not served forsea: agents (no Washington licence roll reaches a commercial requester; 422agents_unavailable). The plan, the measurements and the research aredocs/seattle/PLAN.mdanddocs/seattle/research/. Production serves it since 2026-09-17 (the 2026-09-17 entry). - Docs The auction-kind descriptions gain their Washington clauses (
TRUSTEE_SALE,SHERIFF_SALE,TAX_SALE;SHERIFFS_DEED“in an Arizona or Washington market”), the county FIPS examples and themetrodescription name the Seattle metro (the coverage tool lists the markets from the database and needed no change), everystatedescription of a market row (the coverage row,GET /v1/dataset, the investor objects) says AZ, TX or WA, thelast_sale_price,prior_sale_priceandlast_sale_price_per_sqftnull clauses name the Washington nominal-consideration rule, and the site’s index, coverage guide and handbook say three markets. No contract change: the generated documents are regenerated without a version bump. The attribution line of the Terms and attribution page gains “the City of Seattle (public domain)” after Tempe, a Section 8.2 change of the Developer Agreement (Beta);meta.termsis unchanged (“municipal short-term rental registries” already covers it). The 8.2 notice goes to every account that has accepted the Agreement when the Seattle roll is first served; the acceptance ledger is the console’s, not this repository’s, and the notice is not sent by the build. Recorded 2026-09-17: production first served Seattle at 11:12 UTC. The console’s ledger held no acceptance of any Data Services agreement at 19:30 UTC that day, so the notice went to no account. The first account that accepts the Agreement sees the line as published. - Docs API 0.27.1: seven operations that had no recorded example in the reference carry one, recorded on stage on 2026-09-16
and published in the deals-only shape like every other example (item 22 of the SFR Analytics API review of 2026-09-15):
GET /v1/dataset(every loaded market),POST /v1/properties/search(the three parcels nearest the Phoenix golden point within 2 miles, the default datasets),GET /v1/properties/{property_id}/financingandGET /v1/properties/{property_id}/permitsat the golden parcel (no open lien, one involuntary lien, no permit: the shapes a parcel with nothing on record carries),GET /v1/properties/{property_id}/history(the ten newest events,limit=10) andGET /v1/properties/{property_id}/listing-historyat a Scottsdale condominium of the history lake’s ZIP half a mile from the point, which the golden parcel sits outside of, andGET /v1/markets/{market}/changesfor ZIP 85251 at the default 52 weeks. The comps route is the one documented/v1operation still without an example. No contract change. - Added API 0.27.0 (provisional): over
/mcp,meta.creditson every tool result behind the gateway carriesused,lineandperiod_endbesidecharged: the period’s exact count after the call as the API’s own ledger holds it (the figure the line is enforced on; null when the ledger did not answer or nothing is counted for the request, never a figure derived from the charge), theX-Credits-Linethe gateway forwarded and the period end it resets on.godmode_coveragecarries anaccountblock besidedata.markets[]:plan,tierandperiod_end;creditswithgrant,line,used,remaining(grantminusused, negative on Growth and Scale into the overage down tograntminusline, and on a hard line by at most the last page’s cost; the API’s exact counter, which differs fromX-Credits-Remaining, the gateway’s balance, by the gateway’s one-minute cache and by any call charged for a client that left mid-request, until the period resets) and, on Free, the lifetimebudget(line,spent,remaining);limitswithlargest_limit, the small plans’geometrybound,max_weeksandname_search(day_cap,used_today,resets_in_seconds);requests(per_minute,per_day, a copy of the gateway’s catalogue); andcounters, which says whether the counts were read (read,off,unavailable). Off the gateway the block readsplannull andtierinternalwith no credits. A standing read the ledger did not answer is counted undercredits_ledger_errors_total{op="standing"}, a new label apart fromread, whose every increment stays a 503 to a customer. Item 20, step 1, of the SFR Analytics API review of 2026-09-15; step 2,GET /v1/account, needs the gateway repository. Keys and headers - Added API 0.26.0: holdings on the investor row.
holds_minandholds_maxjoin the shared filters: the investors whose market-wideinvestor.n_holds(confirmed holdings plus un-resold purchases inside the area loaded for the market) sits inside the band, inclusive, 0 to 1,000,000 each; for exampleholds_min=20for the larger portfolios,holds_max=0for the investors holding nothing, and buyers without an investor id pass unlessregistered_only=true. They apply wherever the shared filters do (GET /v1/investors,GET /v1/buyers/match,GET /v1/dealswith its summary, pins and cells) and on the MCP tools that carry the filters (godmode_investors_nearby,godmode_buyers_match,godmode_deals_search,godmode_deals_summary).sort=holds_desconGET /v1/investorsandgodmode_investors_nearbyorders the rows byin_radius.n_holds, the holdings inside the geometry, as every sort of that list orders by what happened there.GET /v1/investors/{id}gainsmedian_purchase_year: the median year the investor bought the parcels it still holds, a whole year computed over then_holdsrows when the profile is read, null when it holds nothing or no holding is dated;godmode_investor_lookupcarries it in both formats. The band reads a new index on the investors’ holdings count (the 132nd of the served set, carried by the pipeline and the publish path). No sort is tier-gated and none becomes so. List investors in an area - Added API 0.25.0:
by_channelonGET /v1/deals/summaryand, through the same plan, ongodmode_deals_summary: five filtered counts over the summary’s row set, each on the deal row’s own channel field,from_wholesaler(bought_from_wholesalertrue),from_investor(bought_from_investortrue: the seller carried an investor id),distressed(bought_distressedtrue),auction(bought_auction_kindset) andother_or_unknown(the rows in none of the four, a deal whose purchase deed is unknown included). The four named buckets are overlapping counts, not a partition: a known wholesaler who is also a registered investor, or a distressed deed bought at auction, counts twice, so the five do not sum ton_deals, andn_dealsminusother_or_unknownis the rows in at least one.other_or_unknownis not a count of purchases from a homeowner, because a contract assignment leaves no deed.auctionis null, never 0, where any market the location covers hasmeta.coverage[].auction_countedfalse, the rule thebought_auction_kindfilter already answers 422 under. The counts are five more aggregates over the one scan the summary already makes (stage, 5 miles at the golden point, 31,987 rows: 231 and 263 ms against 240 and 237 ms before, the same plan). The summary tool’s description names the block and stays under its cap; the recordedgetDealSummaryexample was re-rendered for its own request against the day’s stage tables so it carries a real block. - Changed API 0.24.2: a
/v1problem response no longer carries the dataset freshness headersETag,Last-Modified,X-Dataset-VersionandX-Data-End; they belong to a 2xx and the 304. The handler set them before the query ran, so an error raised after that point (a 403 at the name-search day cap, a 404 from the query, a 422, a 500, a 503, a 504) carried a validator that described no body and the freshness of an answer that was never served, and a shared cache was handed a 503 wearing a dataset’s freshness.Cache-Control: private, no-cachestays on a problem, as before. A compressed export (Accept-Encoding: gzip) whose stream fails after the count now answers its problem without the stream’sX-Row-Cap,Content-DispositionandContent-Encoding, so the body is a readable problem rather than a file labelled gzip. No field, parameter or code changes. - Docs API 0.24.2: the response headers are declared in the reference, each described once under
components.headersand referenced from the responses that carry it (The response envelope). Every response of the operators’ document declaresX-Request-Id; every/v1response the limiter counted, errors included, theX-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Resettrio (the 503 the in-flight cap answers before the request is counted carries none), which the public document leaves out because the gateway never forwards it; every/v12xx and the 304ETag,X-Dataset-VersionandX-Data-End, and every/v12xxX-Rows;Retry-Afteron every 429, on the 503 of every/v1route and on the 403 ofGET /v1/investors/search(the name-search day cap);X-Row-Capon the 200 and the 422 of the routes that export CSV (on the public document, the loans of a lender alone); andGET /v1/datasetdeclares its 304 with the freshness headers it repeats. The public document describesX-Request-Idas the edge sends it (alsozp-rid; absent on a refusal the gateway answers itself) and moves its two credit headers under the samecomponents.headers. No header is new: the set is the nine headers the CORS policy already exposed plus the export cap (item 26 of the SFR Analytics API review of 2026-09-15). The one wire change of the release is the Changed line above. - Docs API 0.24.1: every field that reads as of a date names the date. Fifty-eight descriptions are reworded, fifty-six
fields, filters and blocks of the reference and the agent listings and coverage tool texts. The ones that said “today” or
“right now” (
on_market,owner_testand the agent’sroleon a listing,deal_type,holding_kind,ownership_confirmed, the propertyownerblock,n_active,n_pendingandn_listedon the investor’s listings block, thelisting_statusfilter, among others) now name the listing data end (meta.coverage[].listings_data_end) or the deed data end (meta.coverage[].data_end), the waydays_on_marketdoes;n_active,n_pending,n_on_market,n_listed_12mandn_sold_12mon an agent’s listings block,n_activeon an agent search hit and theholderof an agent’s listing row name the listing slice the agent registry was built from (meta.coverage[].agents_data_end, equal tolistings_data_endwhile the registry is current), which is the slice the registry counts them at; the windows that said “the last N months” or “still is” (the auction and REO outcome counts, the wholesalePENDINGandNO_DEED_120outcomes, the permit and history filters and summaries of the property search, thew_wholesale_buyerandw_activityweights of the buyer match,str_status, a lender’schurn_measured, the profile’s 24-month counts, among others) name theirs. Four tests hold the line from this date: no public document, no hand-written page above its “Partners and staff” section (fenced examples excluded), no public tool description and no changelog entry names a served table, the pipeline’s vocabulary, a market’s table set, the schema, a swap table’s suffix, or the runner’s flag and the placeholder it fills (test/public-openapi.test.ts,test/docs-site.test.ts; the entries dated before this one keep their wording); every field or parameter description that reads relative to the present names its anchor in that sentence, or sits in a block, or inside one, that carries a field named for the date (data_end,listings_data_end, anas_of), with the calendar flags of the short-term rental feed (StrCoverageSource.stale,StrCoverage.feed_stale,StrCoverageJurisdiction.feed_stale,InvestorStr.stale_jurisdictions) allowed by name and the hidden ops probes left out (test/openapi-docs.test.ts); no served SQL reads the clock, by function, keyword, one-argumentage()or the'now'and'today'input strings (test/v1-sql-clock.test.ts). The coverage tool’s description says whatcontact_via_mcpmeans without naming the scope or the header behind it, and says itsstaleflag is the one value it serves that is measured against the request’s day. No contract change. - Added API 0.24.0:
page.cappedon the four name searches,GET /v1/investors/search,GET /v1/agents/search,GET /v1/wholesalers/searchandGET /v1/lenders/search: true when more names matched thanlimitallowed and the page was cut at it, false when every hit is on the page, so a page holding exactlylimithits can be told from the whole set (a ranked lookup issues no cursor, so the response could not say so before). The statement fetches one row past the page, as the keyset lists and the address resolve do; no search counts its hits twice. The MCP lookup toolsgodmode_investor_lookup,godmode_agent_lookup,godmode_wholesaler_lookupandgodmode_lender_lookupcarry the samecappedbesidecandidateson an unresolved name (item 27 of the SFR Analytics API review of 2026-09-15). - Changed API 0.24.0: on
GET /v1/investors/searchthe matched member of a many-member investor is the one with the most purchase deeds (atrigramhit: the most similar spelling, then the most deeds), where it was the first matching key alphabetically;n_deals_minandinclude_publicapply inside every search step, so a broad query that came back with fewer thanlimitrows while more matched now fills the page; and the fuzzy step runs when fewer than 10 investors matched by the better methods, where it counted deed spellings. OnGET /v1/lenders/searchthe fuzzy step keeps one spelling per lender, the most similar, before its cap of 200, where it capped spelling rows, so a capped step holds 200 distinct lenders andpage.cappedis exact on every search. Both are why the flag above can be trusted; additive under the rule (no field, route or code moved). - Added API 0.23.0:
meta.geometrybesidemeta.reference_pointon every route that takes a location:GET /v1/deals,GET /v1/deals/summary,GET /v1/deals/cells,GET /v1/deals/pins,GET /v1/investors,GET /v1/wholesale-listings,GET /v1/str-parcels,GET /v1/buyers/match,GET /v1/properties/{property_id}/comps,POST /v1/properties/searchand, when a geometry, azipor acitywas given,GET /v1/lendersandGET /v1/lenders/{id}/loans. One flat object keyed as the query is (kind,lat,lng,radius_miles,bbox,property_id,zip,city), every field typed null when the kind has no such value, so it can be copied back as the query. It is the location as it ran, defaults filled:radius_miles2 on the GET lists when none was sent and 1 aroundproperty_idon the parcel search; aproperty_idrequest echoed askind: "radius"around the parcel centroid withproperty_idfilled; a viewport as the[west, south, east, north]tuple; a ZIP list underzipas sent and a city as the folded upper-case value that was compared;latandlngthe reference point that rode beside a viewport or a place, null without one. On the parcel search a ZIP-only body echoeskind: "place"and a county-only body echoes nothing; aziporcounty_fipslist beside a geometry is applied as sent and not echoed. The comps echo the subject askind: "radius"around its centroid withproperty_idthe subject andradius_milesthe value the comps were drawn from. The lender list and loans apply a geometry as the H3 res-8 cells whose centre lies inside it, as before, and echo azipor acityaskind: "place"; acountyis not echoed: the echo describes the shape asked. REST only: the MCP tools’metacarriesreference_pointalone.GET /v1/markets/{market}/changescarriesweeks_appliedondatabesidefirst_weekandlast_week: the window that was applied, the weeks asked for or 52 on Free and Starter, so the plan clamp is reported rather than silent. The response envelope - Changed over
/mcp,godmode_market_changesrefusesweeksabove 52 on Free and Starter as403 plan_limitnaming the cap (limit: "weeks",cap: 52,errors[0].paramweeks), as every paged tool refuses alimitover the plan’s page, instead of clamping the series in silence; REST keeps the clamp and reports it inweeks_applied. The tool’s description and itsweeksinput say so, and theplan_limitcatalogue row names the change series’ weeks. Read a ZIP’s weekly changes - Docs the changes page said
first_weekandlast_weekwere the edges of the whole record; they are the edges of the returned window (weeks[0]and the last entry), and the lake’s own edges aremeta.coverage[].parcel.history.first_week/last_week. The two field descriptions, the page and its gotchas say so, and the envelope pages describemeta.geometry. Read a ZIP’s weekly changes - Added API 0.22.3: the MCP prompt
underwrite_property, the seventh besidefind_buyersandmarket_snapshot(item 23 of the SFR Analytics API review of 2026-09-15). For one house at a price (asking_price, thenproperty: aprop_id, alatitude,longitudepair or a street address with its ZIP or city after it) it walks the model throughgodmode_property_lookup(the parcel, its owner and deeds),godmode_property_financing(the open liens, equity and LTV at the slice date),godmode_property_comps(the comparable sales, most similar first, with the flip exits among them),godmode_deals_search(the flips within a mile bought in the twelve months to the market’sdata_end, read off the coverage),godmode_property_search(count_onlyover the parcel’s ZIP with the last sale within 10 percent of the price in the same twelve months: the ZIP’s liquidity) and, where the market’sgodmode_coveragerow carries the lender registry and itslenders.countieslists the parcel’s county,godmode_lenders_topandgodmode_lender_loansfor the hard-money lenders active in the ZIP; it names the figures to carry into the underwrite, themeta.dated[]stamps to repeat and the caveats (the parcel layer is published for one market today, and inside it the financing slice and the lender registry cover Maricopa alone, read off the coverage row; Texas prices are mostly null; nothing here is a valuation). Both hosts list it, so both manifests move; the OpenAPI introduction’s MCP sentence counts seven prompts. No/v1change. Prompts and chaining - Docs API 0.22.2: corrections from the SFR Analytics API review of 2026-09-15 (items 30 and 31). The
DealKinddescription states the flip test as the pipeline applies it (held 30 to 365 days, and either both deeds priced and resold for 10 to 150% over the purchase price or an unpriced purchase resold at a price by a registry investor; a resale under 30 days, or a chain resale or round trip within 90, is wholesale; about one flip in six has no investor id), thekindlegend,by_kind.wholesale, bothn_wholesalesfields andn_quick_resalesstate the wholesale bound in the same words, so both OpenAPI documents move, and the document carriesexternalDocspointing at this site. On the site, The ideas you need carries the same sentence under the deal kinds, and the deal object page’swholesalerow reads the same; Plans and limits says every plan sees every covered market, coverage being a label on the response and not a line on the bill, and names the two products sold outside the plans (GET /v1/str-parcelsper jurisdiction under an order form, the Market Buyer File per metro when it ships); I know the name, find the investor links the merged-id and retired-id handling of the profile walkthrough; and the prompts page no longer says five of the six prompts are on the public host. A unit test now pins every catalogue count a hand-written page or snippet spells out (tools, prompts, resources, the families and the subsets) to the registry, the way the OpenAPI introduction’s sentence already was, and fails on a subset it has no constant for. No contract change. - Docs a walkthrough for the top buyers of a place,
Top buyers in a place: one call,
GET /v1/investors?zip=<one ZIP>&bought_after=<data_end minus 90 days>&sort=events_desc, with the point form (lat,lng,radius_miles) andgodmode_investors_nearbywith the same inputs as the alternatives;bought_afteras an absolute date the reader computes frommeta.coverage[].data_end(the data end runs weeks behind today, so a window counted from today is short by that lag);in_radius.n_purchase_eventsas the trailing count beside the row’s market-widen_deals,n_purchase_events_12manddays_since_last_purchase; the operator rule (rows sharingoperator_idare one operation, counts per id); the contact facts every key sees (contact.n_skip_trace_targets,has_person_member,has_home_address, the identifying fields null on the public host); and what the list is not (a relative window, a ring count the API does not compute, a ZIP list or a city on Free and Starter, buyers without an investor id).GET /v1/investorsgains a Related line to it. Item 19 of the SFR Analytics API review of 2026-09-15. No contract change. - Docs API 0.22.1: a walkthrough for new construction,
New construction near here:
POST /v1/properties/searcharound a point, or around the parcelGET /v1/properties/resolve?addressanswers for a street address, with theactive-listing,new-constructionandcorporate-ownedquicklists, thelistingandvaluationblocks andsort: listing_price_asc; how to read the list price, the days on market (counted to the listing feed’s as-of date,meta.coverage[].listings_data_end, never to today), the assessor’syear_built,owner.kindfor the builder and the dated valuation; what the list is not (Phoenix alone today, with Houston answeringparcels_unavailable; MLS-listed only; a standing parcel as the roll dates it, so a to-be-built plan and a spec home still carried as a lot are absent; a dated AVM); and, for the internal host, the one-builder narrowing withfilters.owner.name_contains_words. Thegodmode_property_searchdescription names the same question (“new-construction homes for sale within a radius”) among what it answers and no longer restates the dataset list itsdatasetsfield carries, so both manifests move (item 8 of the SFR Analytics API review of 2026-09-15). No contract change. - Added API 0.22.0: a ZIP list or a postal city as the fourth location beside the point, the viewport and the parcel on
GET /v1/deals,GET /v1/deals/summary,GET /v1/deals/cells,GET /v1/deals/pins,GET /v1/investors,GET /v1/wholesale-listingsandGET /v1/str-parcels.zipis a comma list or repeated key of 5-digit ZIPs (up to 50);cityis the parcel’s postal city as the county records it, any case (folded upper case; never the short-term rental jurisdiction ofmeta.coverage[].str.jurisdictions[]). One place per request, and not besideradius_miles,bboxorproperty_id(400geometry_conflict);lat+lngmay ride along as the reference point only, sodistance_miles,nearest_distance_milesandsort=distancekeep their rule andmeta.reference_pointechoes the point, while a place with no point takes the list’s default order (sort=distancethere is 400sort_requires_point); no shape is echoed. A ZIP or a city no parcel of a loaded market carries is 422outside_coveragenaming it (zips_unknownorcityin the body) and the loaded markets, and a list with one unknown ZIP is refused whole, so a page is never mistaken for the full set. On Free and Starter the place is exactly one ZIP: a ZIP list or a city there is 403plan_limitnaming the limit. That place rule runs on the summary, the cells and the pins as on the list and the investors, while the radius and viewport bound of those plans stays where it was (the deals list, the investors and the match carry it; the summary, cells and pins never did), so a summary or cells request served under 0.21.0 is served unchanged. A city, or more than six ZIPs, queues behind other large queries like a radius over five miles.GET /v1/buyers/matchtakes no place:ziporcitythere is 400geometry_conflict, because the match ranks near a point. The MCP toolsgodmode_deals_search,godmode_deals_summary,godmode_investors_nearbyandgodmode_wholesale_listingstakezipandcityunder the same rules andgodmode_buyers_matchrefuses them; the concepts resource lists the place beside the three geometries, and the catalogue budget took its fourth raise, by 8 KiB to 176 KiB (173,651 bytes measured;api/test/mcp-registry.test.ts). Two indexes on the parcel table,(market, zip)and(market, city), were built on stage the same day and are carried by the pipeline and the publish path for every later build. List deals around a location - Added API 0.21.0:
GET /v1/properties/resolvetakes a street address:address(the street line as the county records it) withziporcityand an optionalunit, exclusive withlat/lngandapn. The line is normalised by the pipeline’s own normaliser (il_norm_address) and probed against a served address table (one row per parcel with the parts of its situs line, keyed on ZIP, house number and street; built over the live parcel table of each market and swapped like the other sidecars) with the direction-less street as the fallback rung, the ZIP narrowing the probe or, without one, the city. One row resolves; several rows resolve when exactly one row’s unit matches the unit given (inside the line or inunit), otherwise 422ambiguous_addresslists up to 25candidates[]withtruncated.ResolveCandidategainsunit.meta.coverage[](and/health,godmode_coverage,godmode://markets) gainsaddress_as_of, the deed data end the address table was read from, null where it is not published; an address is then 422addresses_unavailable.godmode_property_lookuptakes the same form (address,zip,city,unit; an ambiguous address isresolved: falsewith the units as candidates), and the concepts resource points a model from an address to aproperty_id. Two error codes (43), both bound to the resolve operation in the OpenAPI document along withambiguous_apn, which was declared on every 422 route before. - Added API 0.20.0:
GET /v1/properties/{property_id}/compsand the MCP toolgodmode_property_comps: the comparable sales around one parcel, most similar first. Every priced sale insideradius_miles(default 1, at most 5) and the lastdays_backdays before the market’sdata_end(default 180, at most 1,095) on a parcel withinbeds_tolerance(1),sqft_pct(20) andyear_tolerance(15) of the subject, one sale per parcel, from two sources the API already serves: the deed registry’s arm’s-length last purchase (DEED, every market) and the SOLD listing records where the market’s listing tables are published (MLS, withdays_on_marketandlist_price);sourcepicksdeed,mls(422listings_unavailablewhere none) orboth(the default; the MLS record wins over a deed recorded within 45 days of it). Each comp carriesdistance_miles,price_per_sqft, the building facts,cash_sale_proxyandlast_sale_lenderfrom the parcel layer where the market has one and its last sale is the same transaction, the investor exit the deal registry records within 45 days of the sale (deal: id, kind, investor, gross profit, hold days) or null, andsimilarity(0 to 1: the distance, size, bedroom, age and recency gaps, each over its own tolerance, weighted 0.35, 0.25, 0.15, 0.10 and 0.15).summarycarries the counts by source, the medians, an indicativeprice_per_sqft_value(the median times the subject’s sqft: no time or condition adjustment), the range, the window, the sources read and the tolerances applied (a fact the subject lacks applies none). Priced at 2 credits plus 1 per comp parcel new to the account. The MCP catalogue is twenty-six tools, inside the 168 KiB budget of 0.19.0 (api/test/mcp-registry.test.ts). Read a parcel’s comps - Added API 0.19.0: rankings by place and outcomes of the lender registry (phase 5 of docs/god-mode-api/PLAN-lenders.md; 0.18.0 went
to the cash-sale proxy fix merged the same night), on
the same dated slice (2026-06-25, recordings through 2026-06-11, Maricopa County alone inside the Phoenix market). The
geography table gains the H3 cell kinds (res 7 and 8), the market-wide month kind and the periods
12m,all, a calendar year from 2002 and a calendar month among the 36 to the slice month beside24m(a storedrankover every identity in the bucket, persons below the gate and placeholders included,share_by_volume,n_borrowers,n_foreclosed; 3,862,332 rows on stage against 70,119, cell8 1,293,029, zip 1,186,445, cell7 686,395, city 521,563, county 143,865, month 31,035).GET /v1/lenderstakes every period class inside a ZIP, city or county (market-wide24mand a calendar month, which reads the month kind; 400validation_errorcodeperiod_not_servedotherwise) and a geometry (lat+lng+radius_miles,bboxorproperty_id, the shared fields; not beside a place, 400geography_conflict): a geometry is resolved to the H3 res-8 cells whose centre lies inside it (about 0.3 sq mi each), so its edge is a hex boundary, not the circle, and the lender’s cell rows are summed over the three windows alone (share,share_by_volume,n_unpriced,n_borrowersandn_deals_financednull there, not measured over an arbitrary set of cells;in_geometry{res, n_cells, n_cells_lent}on every row; outside every market 422outside_coverage, in another market 400geometry_market); the ranking row carriesshare_by_volumeandn_borrowers. Two routes, zero-priced:GET /v1/lenders/{id}/rankings(group_bymonth, zip, city, county or cell withres8 or 7,period,min_n10 on the lender’s own count, a keyset page; the Lender ranking bucket\{key, n, volume, n_purchase_money, n_investor, rank, share, partial, floor, as_of, dated\}, the month series served as a span with every month present, zero-filled, andpartialon the slice month and year,flooron a year beforehistory_floor_year) andGET /v1/lenders/{id}/cells(res, a window period,product[]narrowingn; the Lender cell\{cell, n, volume, n_purchase_money, n_investor, n_foreclosed, as_of, dated\}, up to 5,000 a page in cell order, served from the result cache on a repeat). The profile carriestakebacks(the take-back deeds since 2016 joined to the registry through its member keys:n_takebacks,n_takebacks_24m,by_instrument[]TRUSTEES_DEED/SHERIFFS_DEED/DEED_IN_LIEU,n_loans_foreclosed,n_takebacks_unlinked,foreclosure_sharenull under 20 captured loans,median_days_takeback_to_resale,is_auction_lenderread from the auction build’s lender list alone, never the buyer-side exclusion vocabulary; deeds, never delinquency, the grantee usually the trustee or servicer; null whiletakebacks_measuredis false) andrankingsgainsshare_by_volume_24m,n_zips_ranked_first_24m,n_cities_ranked_first_24mandn_counties_ranked_first_24m(the lender’s own 10 or more and rank 1; the county count null whilecounties_measuredis false); the loan row carriesforeclosed(instrument,deed_date,buyer_is_lender: the earliest take-back deed on the parcel more than 90 days after the recording that precedes any priced resale and any later mortgage; a shorter gap is the auction buyer’s own deed, not a foreclosure of this loan) andGET /v1/lenders/{id}/loanstakesforeclosed,cell(an H3 index at res 7 or 8, the resolution read off its bits) and the same geometry, the CSV three cells (Foreclosed on, Foreclosed instrument, Foreclosed by lender); the borrower row carriesother_lenders[](the stubs behindother_lender_ids[], which stays); the wholesale listing carrieslenders_active[](the five lenders with the most investor loans in the listing’s ZIP over the registry’s window, from the ZIP rankings at page time; empty where the ZIP has none, null on a hidden address and where the registry or its borrower match is not published; no CSV cell) and its responses carry the lenders and financing stamps inmeta.dated[];meta.coverage[].lenderscarriestakebacks_measuredandcounties_measured. Over MCPgodmode_lenders_nearbyjoins the catalogue on both hosts (the ranked lenders inside a geometry, the investors_nearby shape;/rankingsand/cellsare not tools, the pins and cells precedent) andgodmode_lenders_toptakesperiod: twenty-five tools; the catalogue budget took its third 8 KiB raise, to 172,032 (166,431 bytes measured, the arithmetic intest/mcp-registry.test.ts); the wholesale tools’ concise row leaveslenders_activeto the detailed format, the loans tool’sforeclosedand the borrowers tool’sother_lenderslikewise. Every phase-5 field is null and every phase-5 parameter and route 422lenders_unavailableon a registry built before the place rankings or a host without the h3 extension (MarketCache.hasLenderPlaces;scripts/sql/roles.sqlcreatesh3andh3_postgis, an operator step of docs/data-services/RUNBOOK-copy.md and deploy/README.md); the publish path: seq 42, 45 and 46’s md5s moved,indexes.sqlstays 124; nothing is on production until the family’s next copy. None of it is priced, and the early-access sentence of 0.14.0 still applies. The Lender ranking bucket - Changed API 0.19.0:
outcomeon a loan tied to a deal readsFORECLOSEDwhere a take-back deed on the parcel (a trustee’s deed, a sheriff’s deed or a deed in lieu, dated more than 90 days after the recording: a shorter gap is the auction buyer’s own deed, not a foreclosure of this loan) precedes any priced resale and any later mortgage of another lender, and a distressed deed no longer counts as the resale of the chain (a trustee’s deed carries a credit-bid price and readRESOLVED_BY_RESALEbefore): on stage 231 captured loans’outcomemoved against the phase-4 set: 40 toFORECLOSED(30 fromRESOLVED_BY_RESALE, 7 fromOPEN, 3 fromREFINANCED), and 191 more leftRESOLVED_BY_RESALEonce a distressed deed stopped counting as the resale (151 toREFINANCED, 23 toUNKNOWN, 17 toOPEN);months_to_resolutionis then measured to the take-back deed,by_outcome_24mon the profile carries the value, and acceptance 12’s “UNKNOWN on at most 10%” was re-read after the move (793 of 58,940 captured, 1.35%, from 770 before).geography.by_county[],by_city[]andby_zip[]on the profile count dated instruments alone inn_all, now that the geography table’sallperiod is the dated ones (an instrument without a recording date is counted in the profile’sn_undated, not in any place), and a place whose instruments are all undated no longer appears. The Lender loan - Data The phase-5 stage build (from the runner as the chain’s
lenders,parcel_serveandpublic_copystages on the db.m7g.2xlarge, runs 35050752528 and 35053303132: 35b step 412 at 90.8 s, 35c 413 at 1,874.3 s after a first attempt (411, 1,447.6 s) raised on the geo share rounded to 0, 30a 414 at 123.3 s, 22c 21.2 s: 35c alone under caffeinate after a 1/256 dry run of the one-pass roll-up, then 22c; the report lines pinned at this build: LAKEVIEW 170 (165 trustee’s deeds, 5 in lieu) take-back deeds (165 expected), BANK OF NEW YORK MELLON 187 (159 trustee’s, 26 in lieu, 2 sheriff’s; the plan’s 77 sit on BANK OF NEW YORK’s own identity) (159 + 77), WELLS FARGO 165 (133 trustee’s, 27 in lieu, 5 sheriff’s over the two member spellings) (107 + 26), CSTE HOLDINGS 462 deeds excluded pending review (462), the auction lender table 233 keys (120 DIL_GRANTEE_2P, 113 CREDIT_BID_2P),n_takebacks_unlinked6,168 of 6,586 (418 linked to a loan row of the lender; 126 served identities readis_auction_lender) market-wide): 32,046 identities served, lender registry run 16, dataset_version 1789530735 (the market row restamped 1789532610).
-
Changed API 0.18.0:
cash_sale_proxyon the parcel row is null on every parcel of a market whose delivery records a purchase mortgage on fewer than one priced last sale in five, instead of true on every priced sale. The 2026 deliveries carry that column empty, so thecash-buyerquicklist and thesale.cash_salefilter had been returning every priced sale of the last three years as a cash sale (299,430 of 299,430 in Phoenix on the stage tables of 2026-09-14). Where the flag is unmeasured both now answer 422cash_sale_unavailable; the measured cash signals (the auction block,bought_auction_kind,buys_at_auction,buys_reo) are unchanged. The served value moves on the next 30a build of the market. Parcel search row -
Added API 0.18.0:
meta.coverage[].parcel.sale_mortgage_measured, true when the delivery records a purchase mortgage on at least one priced last sale in five of the market; and the error codecash_sale_unavailable. Errors -
Added API 0.17.0: identity and class of the lender registry (phase 4 of docs/god-mode-api/PLAN-lenders.md), on the
same dated slice (2026-06-25, recordings through 2026-06-11, Maricopa County alone inside the Phoenix market). Lender ids
now follow the alias table (
gm_lender_alias, the seventh lender table:old_id,new_id,reasonandlender_run, the run that retired the id): an id retired by a merge redirects to the current lender withmeta.resolved_fromonGET /v1/lenders/{id},GET /v1/lenders/{id}/loansandGET /v1/lenders/{id}/borrowersand their MCP tools (godmode_lender_lookup,godmode_lender_loans,godmode_lender_borrowers), the current id bound by the scan, the cursor and the CSV filename; a retired id with no successor is410 gone(superseded_bynull, the reason and the lender registry run in the body; the three id routes and the parcel search declare the status). The walk runs only after the route’s own lookup found no row and only while the registry carries the alias table, so a registry built before this phase answers 404 for an old id as it did, and a lender live in several markets answers them all asmarkets[]on the profile (nevermarket_requiredfrom the walk; the list routes keep it). The two lender-id filters walk it too:financed_byonGET /v1/investors,GET /v1/deals(and/pins,/cells,/summary) andGET /v1/buyers/match, andfilters.financing.lender_idonPOST /v1/properties/search, bind the current id behind a merged one and stampmeta.resolved_fromafter anyinvestor_idchain, answer410 gonefor a retired id and, as theinvestor_idfilter does,404 not_foundfor an id no registry run ever issued (until now the id was bound as sent and matched nothing). The profile carriesclass_basis(NAME: the registry’s name rules read on the folded name;DICTIONARY: one of the seven hand-kept CLASS rows overrides them, for example BBVA USA as a BANK),successor(the acquirer or parent a link row records:id,name,lender_class,is_hard_money,kindACQUISITIONorSUBSIDIARY; the books are never merged, so a lender’s loans stay under its own id; null when none is recorded or the successor has no profile in the market; 19 link rows seeded by hand, Chase Manhattan and National City into JPMORGAN CHASE BANK and PNC BANK, Wells Fargo Financial and SunTrust Mortgage into WELLS FARGO BANK and TRUIST BANK, M&I into BMO HARRIS BANK among them, every acquirer or parent a served identity of phx),nmls(id,url: the NMLS Consumer Access page; null where uncurated; 69 ids curated by hand, most of the top 80 served lenders by loans in the window and the golden lender KIAVI FUNDING, each verified on 2026-09-15 against the lender’s own licensing disclosure or NMLS Consumer Access, the top 500 the target and the build printing the backlog) andid_history[](old_id,reason,lender_run: the ids merged into the lender, oldest run first, each still answering with a redirect);class_basisandnmlsride the Lender object too, the lead market’s; every one of them is null (the history empty) on a registry built before this phase.meta.resolved_fromadmits alen_id beside the investor and agent ids, and410 gonenames the three kinds. Over MCP the lookup forwardsmeta.resolved_from, itslender_idinput says an old id is followed, and the concise format keeps the four fields; the catalogue measures 160,132 bytes against the 163,840 budget (the arithmetic intest/mcp-registry.test.ts). On stage the build retired 118 ids with alias rows (120 rows ingm_lender_alias: the 108 served ids dictionary round two (116 estimated before the run) merged into their canonicals, the person-gate crossings, and the Sierra Pacific pair carried since the phase-1 build); the seventh table rides the parcel family’s publish path (22c seq 48 beside seq 42’s moved md5, rules 1515 to 1517,schema.sqlforty-four tables,indexes.sql124,assert_no_contact.sqlforty-threeCOLUMNSrows); nothing is on production until the family’s next copy. None of it is priced, and the early-access sentence of 0.14.0 still applies. The Lender -
Changed API 0.17.0: the lender classes on
gm_financing,gm_financing_mortgage,gm_mortgage_history,gm_parcel,gm_lenderandgm_lender_loanmoved to the v2 name rules of docs/god-mode-api/PLAN-lenders.md 2.2 (the government, bank, trust, lending and person vocabularies in that order, read on the folded name, so KS STATEBANK, MANUFACTURERS & TRADERS TR CO and SCME MORTGAGE BANKERS read BANK while a person named Banks does not, a securitisation trust reads NONBANK, a custodial IRA account name reads PRIVATE under the person gate, a five-or-more-token name joined by AND or a marital phrase reads INDIVIDUAL, and HOUSING & URBAN FINANCIAL is no HUD form). Measured read-only over the 76,216 phx names before the run with the function the run applies: 4,327 recordings and 3,154 open positions on 212 names INDIVIDUAL to BANK, 4,965 and 400 on 265 names INDIVIDUAL to GOVERNMENT, 12,015 and 6,607 on 2,747 names INDIVIDUAL to NONBANK, 11,676 and 5,436 on 134 names NONBANK to BANK; 43 rows of 1,713,124 leave BANK (20 names, 0.0025%, inside the 0.1% band 35a guards); the 23-row crosstab is in the build log with the run’s own, which must equal it. Placeholder lender names (NOT AVAILABLE, NOT PROVIDED, CORELOGIC SOLEX, UNKNOWN: 4 spellings, 349 recorded and 1,062 open rows on stage (230 and 424 on the four named spellings before the run; the function reads the hyphen and the empty forms too)) are served aslender_namenull withlender_classnull on the financing block, the open liens and the mortgage history, and carry no lender id; the rows stay (their amounts and dates are real) andparcel.financing.n_lendersno longer counts them. The person gate is re-baselined under v2: 669 served person profiles (INDIVIDUAL and PRIVATE) to 493 (503 measured before the run), 10 ids retired with alias rows (10 measured: persons named Banks and two trust-company typos v2 reads below the gate) and 2,729 issued (about 2,757 measured) as names crossed the gate; theprivate-lender-financedquicklist moved from 38,141 parcels to 30,629 (30a’s report). Every id a dictionary merge retired answers a redirect where it answered 404: the 116 served ids round two merged into their canonicals (the HUD-shaped and Department spellings into SECRETARY OF HOUSING AND URBAN DEVELOPMENT, DEPARTMENT OF HOUSING & URBAN DEVlen_992e364eeaebamong them; the 42 Arizona Industrial Development Authority typo spellings with 3 or more rows; the Sierra Pacific leftovers; MORGAN STANLEY MORTGAGE CAPITAL; FIRST INTERNATIONAL BANK & TRUST; the ambiguous truncations decided where every candidate names one lender), and the Sierra Pacific pair (len_6ac531f2070dandlen_04303f5320d7) retired at the phase-1 build, whose two alias rows now carry the run that retired them (4, not the computing run). Financing -
Changed API 0.17.0: over MCP,
godmode_property_lookupcarriesmeta.dated[](thelendersandfinancingstamps) on a detailed lookup whosedeals[]rows carry the financing block of 0.16.0, asGET /v1/properties/{property_id}and the deal and investor tools already do, so the tool’s meta now agrees with 0.16.0’s contract; a concise lookup, which leaves the block to the detailed format, carries none, and a registry without the deed link givesfinancingnull with no stamp. The tool’s description names the block and the stamps. Nothing is priced. (Issue #46.) The twenty-four tools -
Data The phase-4 stage build (phx_step_log 404 for 35a, 406 for 35b, 407 for 35c, 408 for 30a and 409 for 35a again with the review’s guards, 22c at 21:35 and 21:40 ET on 2026-09-15 and 2026-09-16; the two guard raises of 403 and 405 in the build log: the read-only class crosstab, then 35a, 35b, 35c and 30a, then 22c
with a test
gm_lendersuppression row,assert_no_contact.sqlandcheck-no-person-names.mjsover the copy): 32,046 identities served, lender registry run 14, dataset_version 1789520874. -
Added API 0.16.0: the linkage blocks and the borrowers of the lender registry (phase 3 of
docs/god-mode-api/PLAN-lenders.md), on the same dated slice (2026-06-25, recordings through 2026-06-11, Maricopa
County alone inside the Phoenix market). The recorded deeds are joined to every loan: each loan row of
GET /v1/lenders/{id}/loanscarriespurpose(PURCHASEwhen a priced deed on the parcel is dated in the 45 days up to the recording,NOT_PURCHASEwhen the registry covers those days and none falls in them,UNKNOWNotherwise; decided by the deeds, never by the file’s label),deed(dt,price,loan_to_price) on aPURCHASErow,deal(the investor deal the loan is tied to:deal_id,kind,link_windowSTRICTorLOOSE,is_purchase_loan,secondary_loans_n, the investor, the prices,gross_profit,hold_days),outcome(RESOLVED_BY_RESALE,REFINANCED,OPEN,UNKNOWN;FORECLOSEDreserved for a later release) withmonths_to_resolution, and the borrowers as keys and ids (borrower_keys[], 12-hex salted hashes served to every key,borrower_match,investor_id,co_borrower_investor_ids[]); the route filters onpurpose,outcome,deal_kindandinvestor_id, and the CSV gains the eleven cells (Purpose, Deed date, Deed price, Loan to price, Deal id, Deal kind, Purchase loan, Outcome, Investor id, Borrower keys, Borrower match). The profile (GET /v1/lenders/{id}) carries three blocks built at build time and null where their input is not built for the market:borrowers(n_borrowers,n_borrowers_24m,n_resolved_24m,borrower_measured_share,n_repeat_24m,n_new_12m,n_churned_12m,churn_measured, tentop_borrowersnever carrying a person’s name, tencompeting_lenders,person_names_redacted),investor_lending(the investor loans and their share, the investors by kind, the deals financed by what happened on them, the auction and wholesale purchases among them, tentop_investors,is_investor_lender) andflips_financed(the flips whose purchase loan was the lender’s in the 36 months to 45 days beforerecordings_through,capture_share,n_flips_by_known_borrowers,share_of_financed_flips, the resale marginsmedian_gross_profitwithp25andp75,median_spread_ratio,median_hold_days,median_loan_to_price, null under 20 resold flips with both prices,by_year), besideby_purpose_24m,by_outcome_24m,median_months_to_payoff,terms.loan_to_price,rankings.rank_purchase_24m, the flat countsn_purchase_money_24m,n_investor_loans_24m,n_borrowers,n_borrowers_24mandis_investor_lender,by_year[]’sn_purchaseandn_investor,by_month[]’sn_purchase_money, andborrowers_measuredbeside the three measured flags;is_hard_moneyis now true by behaviour too (20 or more loans since 2015 of which a quarter or more sit within 45 days of a registered investor’s purchase, the lender not a bank:hard_money_basisBEHAVIOURorBOTH, 89 identities on stage).GET /v1/lenders/{id}/borrowers, the Lender borrower object: one row per borrower key with the name (a company’s to every key, a person’s under thecontactscope withparty_key), the investor stub,borrower_match, the loans by window,volume_24m,first_loan_on,last_loan_on,n_lenders_used_24m,other_lender_ids[],churned,churn_measured,moved_to_lender_id(a lower bound of leaving on a feed that sees one loan in five) andn_deals_financed;period(24m,all),q(a company name; persons never match),registered_only,churned,investor_kind,min_loans, sortsloans_desc,volume_desc,last_loan_desc,churned_first; the audit line under the contact scope as on the loans route.GET /v1/lenderstakespurpose=purchase(refused besidepositionorproduct),investor_onlyandsort=purchase_desc, and the ranking row carriesn_purchase_money,n_investorandn_deals_financed. The investor profile (GET /v1/investors/{id}) carriesfinancing(n_loans,n_loans_12m,n_loans_24m,n_lenders, tenlenders_used,last_loan,n_deals_measured_24m,n_deals_financed_24m,share_deals_financed_24m,uses_hard_money,as_of,dated) and the deal row carriesfinancing(purchase_loan_statusFOUND,NONE_CAPTUREDorNOT_MEASURED,purchase_loan,secondary_loans_n,link_window,outcome,as_of,dated), both served to every key and null where the registry or its linkage is not published;NONE_CAPTUREDis documented as not cash, since the open-lien table sees about one purchase loan in five on holds and one in fifteen on flips (cash-buyerkeeps reading the sale).GET /v1/investors,GET /v1/buyers/matchandGET /v1/dealstakefinanced_by=len_...anduses_private_lender=true(a loan in the registry’s 24-month window from a hard-money, investor, INDIVIDUAL or PRIVATE lender), and the buyers match adds thefinancingreason (w_financing, weight 0 by default: the investor borrows from a lender that also lends in the subject’s ZIP). Every one of them answers422 lenders_unavailablewhere the registry, its deed link or its borrower match is not published (a registry built before this release keeps serving phases 1 and 2 with the new fields null). Over MCPgodmode_lender_borrowersjoins the catalogue on both hosts: twenty-four tools; a list tool, so a person’s name and everyparty_keyare null on its rows andmeta.contactreadsnot_on_lists; the loans tool keepspurpose,deed,deal,outcome,investor_idin the concise format and drops the two borrower arrays,months_to_resolution,borrower_match, the deed’s date and the deal’s own facts (the default page stays under 20,480 bytes); the lookup caps the three blocks’ lists at five (three flip years); the concise deal row of the deal tools and the parcel leavesfinancingto the detailed format (twenty maximal rows carrying its null key measured 20,736 bytes against the 20,480-byte page); the catalogue budget took a second raise, to 163,840 bytes, with the arithmetic intest/mcp-registry.test.ts. None of it is priced, and the early-access sentence of 0.14.0 still applies. On stage the build linked 44,132 deals STRICT (2,059 flips bought since 2022 with a purchase loan over 495 lenders), 4,599,640 borrower rows over 28,640 lenders, and 10.7% of the history rows since 2019 to a registered investor; the sixth tablegm_lender_borrowerrides the parcel family’s publish path (22c seq 47, rules 1514, 1532 to 1534 and 1591 to 1597,indexes.sql122); nothing is on production until the family’s first copy. The Lender borrower -
Added API 0.15.0: the loan-level contact projection and the parcel-side links of the lender registry (phase 2 of
docs/god-mode-api/PLAN-lenders.md).
GET /v1/lenders/{id}/loansanswersAccept: text/csv: every filtered row aslender-<id>-loans.csvin the requested sort,limitandcursorignored, the set counted before the first row streams, at most 50,000 rows (422csv_cap_exceededabove it, a public code from today on this operation alone), at 0 credits; onapi.investorlift.comfrom Growth (403plan_limitbelow it) withrecorded_fromrequired (400validation_error, codewindow_required, without it;recorded_tostays optional) from the gateway release that forwardsAccepton this route (until it ships the public host answers the JSON page whateverAcceptsays; this release is the origin’s half), on the internal host with neither gate. Every loan row carriesborrowers[]under thecontactscope: a RECORDED row’s borrowers as the recorded history names them (an OPEN row reads null), withcontact_redactedfalse; a deals-only key’s row omits both keys, and the CSV’s Borrowers cell (the names joined by;) is a contact cell dropped the same way. The Financing object carries the registry id beside every lender name,summary.first_lien_lender_id,summary.last_mortgage_lender_id,open_liens[].lender_idandmortgage_history[].lender_id(null where the lender is below the person gate, on a placeholder row and where the market has no registry), so a parcel’s financing block opens the lender and its loans;POST /v1/properties/searchtakesfilters.financing.lender_idbesidelender_name_contains, the parcels whose last recorded mortgage’s lender carries the id, every spelling at once, over a newgm_parcelindex (400validation_errorwhen no market of the location has a registry).GET /v1/lendersservesposition=1withproduct, the first liens of the named products from precomputed counts, which 0.14.0 refused asnot_precomputed. Over MCPgodmode_lender_loansjoins the catalogue on both hosts: twenty-three tools; a list tool, so the borrower keys are left out of its rows andmeta.contactreadsnot_on_lists. None of it is priced, and the early-access sentence of 0.14.0 still applies. The twogm_parcelcolumns behind the filter and the quicklist (last_mortgage_lender_id,last_mortgage_hard_money, from 30a) ride the parcel family’s publish path. List one lender’s loans -
Changed API 0.15.0: the
private-lender-financedquicklist reads the lender registry’s hard-money flag beside the class: a parcel whose last recorded mortgage came from a PRIVATE or INDIVIDUAL lender, or from a hard-money lender of the registry (is_hard_money), 30,666 to 38,141 parcels on stage (7,475 more, both counted 2026-09-15: the class alone on the 0.14.0 build, then the class orlast_mortgage_hard_moneyafter 30a wrote the flag; 7,555 parcels carry it).cash-buyeris unchanged: it reads the sale, never the history. Quicklists -
Changed API 0.15.0:
amount_minandamount_maxonGET /v1/lenders/{id}/loansaccept any non-negative integer; the 2,000,000,000 ceiling of 0.14.0 is gone (a value above it answered 400validation_errorbefore). List one lender’s loans -
Added API 0.14.0: the lender registry, built from the 2026-06-25 financing slice (recordings through 2026-06-11) and
dated like it, Maricopa County only inside the Phoenix market (Pinal is not measured):
GET /v1/lenders/search(any spelling of a lender’s name to one stablelen_id),GET /v1/lenders(the lenders of a market or of one ZIP, city or county ranked by loans recorded in the 24 months to the slice date, withhard_money,position,productandsort=volume_desc; persons and GOVERNMENT hidden unless asked),GET /v1/lenders/{id}(the profile: every spelling, the class and the hard-money flag, the loans counted once across the open liens and the recorded history, the open book,by_yearandby_monthagainst the market, the product, term, label and property-type mix,termswith their coverage,geography,coverage,rankingsand asummary, one profile per market; every volume and the amount quartiles leave out amounts above $5,000,000, a facility amount recorded on every parcel it covers, whichn_amount_excluded_24mcounts andamount_outliermarks on the loan row) andGET /v1/lenders/{id}/loans(every instrument, one row per instrument by default, JSON only), with the Lender, Lender ranking row and Lender loan objects, their reference pages and the walkthrough;meta.coverage[].lenders(as_of,recordings_through,counties,history_capture_share,n_lender_ids: the lender ids served, besideparcel.financing.n_lenders, which counts raw spellings and now says so) and thelendersblock ofmeta.dated[]; 422lenders_unavailablewhere the registry is not published, 422outside_coveragefor a ZIP, city or county outside the covered counties. The recorded history captures about one open loan in five, so every origination count is over the union of the two tables, a history count is a floor and a bridge lender is the most undercounted. Lender names are business records served to every key; a person-named lender has a profile only with 10 or more loans on 5 or more parcels and is hidden unlessinclude_persons=true; nothing carries a lender’s phone or email. Over MCPgodmode_lender_lookupandgodmode_lenders_topjoin the catalogue on both hosts: twenty-two tools. None of it is priced: no credits are charged, the responses carry noX-Credits-Charged, and a legacy MCP call to the two tools weighs 0; when the paid plans publish the lender routes are included from Growth and refused below it at the origin, announced here first. In early access for Investorlift’s team and trusted partners while the developer tier is in beta, to be restricted further before the tier opens to every key. The five tables ride the parcel family’s publish path (22c seq 42 to 46, rules 1510 to 1531 and 1590, thegm_lendertakedown kind); nothing is on production until the family’s first copy. The Lender object -
Changed API 0.14.0: the three spellings under which the financing slice records HUD (
SECRETARY/HOUSING & URBAN DEV,SECRETARY OF HOUSING AND URBANand a third truncation) and 24 rarer HUD forms readlender_classGOVERNMENT, not INDIVIDUAL or NONBANK: the slash and the truncation had defeated the class rule, which gains one alternation (27 spellings, 21,245 recordings and 1,198 open liens on the stage build of 2026-09-15; BANK unchanged). No id changes. Theprivate-lender-financedquicklist (43,623 to 30,666 parcels on stage) and thefinancing.lender_classfilter stop counting those rows as a person’s, and the rows leave the default lender lists with the other GOVERNMENT lenders. Financing -
Changed API 0.14.0: the recovery sentence of
400 market_required(on every caller, investor and agent ids included) and the descriptions ofinvalid_id,not_foundandoutside_coveragenow name the lender list and thelen_id kind beside the kinds they already named; the codes and statuses are unchanged. Errors -
Docs the
positionof a recorded mortgage (mortgage_history[]on the Financing object) is a slot in the recorded history, 1 the oldest kept slot and the highest filled slot the newest, so a parcel with slot 5 filled has lost older mortgages; the reference read “1 is the most recent”.sale_dateon the same record is the loan’s own date as the file writes it, not the sale the mortgage financed. No contract change. Financing -
Docs the legal short forms name the lender family: Developer Agreement 9.1 and Privacy Notice 8 list a person named as
a borrower on a mortgage or as a party to a lien among the withheld fields, and an entity named as a borrower and the
lender of record on a recorded mortgage (which can be a person’s name, with the count and balance of the loans recorded
under it) among the shown ones; Privacy Notice 10 adds the lender record to the removals it offers and says a suppressed
lender’s name leaves every loan, property and history-event record with the amounts and dates kept; Privacy Notice 14
adds a lender’s name as recorded to the identifiers; Acceptable Use Policy 3.6 says lender data describes recorded
activity, not any borrower’s creditworthiness; Developer Agreement 7.2 adds len_ ids to the identifiers a developer may
keep as references. Under the unpublished version
data-services-beta-v2026-09-13, for counsel’s read. No contract change. Terms
- Added API 0.13.0: the listing-agent and Investorlift listing routes are back in the public document
(
openapi.public.json, the reference at developers.investorlift.com) and on the developer tier, in early access for Investorlift’s team and trusted partners while the tier is in beta:GET /v1/agents/search,GET /v1/agents/{id},GET /v1/agents/{id}/listings,GET /v1/wholesale-listings,GET /v1/wholesale-listings/{id},GET /v1/investors/{id}/wholesale-purchases,GET /v1/wholesalers/search,GET /v1/wholesalers/{id}andGET /v1/wholesalers/{id}/listings, with the Listing agent, Investorlift listing and Wholesaler objects, their reference pages and the two walkthroughs; 422agents_unavailablerejoins the public catalogue. Over MCP the five listing-agent and Investorlift-listing tools (godmode_agent_lookup,godmode_agent_listings,godmode_wholesale_listings,godmode_wholesale_listing_get,godmode_wholesaler_lookup) and theinvestorlift_listing_outcomeprompt are served at api.investorlift.com, andmarket_snapshotkeeps its Investorlift step there: twenty tools and six prompts on both hosts. None of it is priced: no credits are charged, the responses carry noX-Credits-Charged, and a legacy MCP call to those tools weighs 0. The fields that identify a natural person stay withheld on this host as on every other route. These routes will be restricted further before the tier opens to every key, announced here first.GET /v1/deals/pinsstays on the internal host. The gateway’s route table follows in the api-gateway repository. API reference - Changed API 0.13.0: deals gain the kind
build: a lot or a teardown bought by a registered investor, a house built on it during the hold, then sold. Those deals wereother; a client that switches onkindshould treat a value it does not know asother. On the stage tables of 2026-09-14 that is 10,006 of Phoenix’s 89,129otherdeals and 10,030 of Houston’s 42,160, served since the publish of 2026-09-17. The classifier labeldeal_kindgainsBUILD. A purchase of a builder’s finished home that is later resold staysother.kindfilters acceptbuild;meta.kindson pins lists it last, so the existing indexes 0 to 4 are unchanged. No deal id changes. (docs/god-mode-api/PLAN-build-kind.md; the bounds of 2026-09-15 narrow this set: see 0.33.1, 2026-09-17.) Deal - Added API 0.13.0:
by_kind.buildand a per-yearbuildcount onGET /v1/deals/summary,n_buildsonGET /v1/deals/cells, andn_buildsonGET /v1/investors/{id}. Each reads 0 for a market whose tables predate the kind. Deals summary - Data Production serves the refreshed Phoenix market:
phxdata_end 2026-08-27 (was 2026-08-12), registry_run 10, dataset_version 1789409315;houunchanged at data_end 2026-08-11 (registry_run 5, re-stamped 1789408252 by the swap). Published from the stage RDS withscripts/copy-gm-from-stage.sh(37 tables, a 1.72 GB dump, 1,188 s end to end; the six served indexes ofscripts/sql/indexes.sqland the float8gm_scoreoffunctions.sqlreached prod with it). API 0.11.0 was released to stage and to production the same day (build log, 2026-09-14).
- Changed API 0.10.0: the MCP tools describe the 29 shared filters and the geometry and paging fields in one sentence
each (
FILTER_SHORT_DESCRIPTIONSbeside the filter table inapi/src/schemas/common.ts); the REST reference keeps the full texts andopenapi.jsonis byte-identical.tools/listis 142,577 bytes compact, from 152,409, and the registry test pins it under 147,456 with no tool over 26,624. Four tool descriptions (godmode_deals_search,godmode_investors_nearby,godmode_investor_deals,godmode_agent_listings) no longer repeat the limit and cursor rules their fields carry. (Efficiency review 2026-09-13, finding F04.) MCP tools - Added API 0.10.0: an in-process result cache for
GET /v1/deals/summary,GET /v1/deals/cellsand thegodmode_deals_summarytool, keyed on the operation, the canonical validated query and the markets’dataset_version, so a data refresh changes every key and empties the store;RESULT_CACHE_MAX_MBsizes it (default 32, 0 disables). A cached answer’smeta.generated_atis the time it was served.GET /healthgainsresult_cache;/metricsgainsresult_cache_lookups_total{outcome},result_cache_bytesandresult_cache_entries. (Finding F05.) Running the API, section 5 - Changed API 0.10.0: the request pool caps a query at 15 s client-side, above the 10 s server
statement_timeout, so a socket the server stopped answering on fails as 503database_unavailableand leaves the pool instead of holding its slot until TCP gives up; the CSV stream carries its own 65 s budget above the 60 s wall clock. The pool keeps two warm connections for ten minutes and parses the RDS certificate bundle once per process. CSV exports are written as the header followed by about 64 KB chunks; the bytes are unchanged. The startup guard tests that schemacoreexists before asking for USAGE on it, so the API starts against a served-tier database with no BatchData feed. (Findings F70, F28, F48, F19, F104.) - Data The stage-to-prod publish can run inside the VPC from the self-hosted runner:
publish-to-production.yml(modesno-swap,swap-only,full,reload; the dump parked in S3 between the two halves; gzip level 1 through the newDUMP_COMPRESSsetting ofpublish-dataset.sh, whose laptop path and--from-dumprollback are unchanged). Fenced behind the publish login, the runner route and the bucket that devops still has to create (deploy/README.md, “Devops asks from the efficiency review (2026-09-13)”). No contract change. (Finding F13.) - Docs Runbook entries for the stage instance’s EBS balance check and the fenced db.m7g.2xlarge refresh bracket, and the planned cutover of the served tier to a dedicated RDS. Nothing built. (Findings F102, F104.)
- Changed API 0.10.0:
POST /v1/properties/searchsortid(the default without a point orproperty_id) orders by market, then parcel id, the table’s primary key order, so a first page over a wide location and every page after it walk the index (stage EXPLAIN over county 04013: cost 291,655 as a parallel seq scan and sort of 1.6M rows, 228 as an index walk). A cursor minted under the old single-key order is refused as 400invalid_cursorwith “Restart from page 1 without a cursor.”; cursors stay opaque. (Finding F03.) Search parcels - Changed API 0.10.0, no wire change: every radius is queried as the bounding box plus the sphere-distance bound instead of
ST_DWithin, so distance-sorted pages run as KNN index walks (the 2 mi deals page from cost 33,502 to 7,725, the 20 mi page from 2,150,105 to 1,573); thestr_status,str_business_use,bought_on_investorlift,source=investorlift,buys_wholesale,buys_at_auctionandbuys_reofilters are hashed sets over the markets in play instead of per-row probes; nullable sorts rendernulls lastnatively and the sort indexes are declared to match; distance and primary-key cursors compare as one row; and the CSV exports count over the scan alone before streaming (the 20 mi deals count from 2,152,417 to 66,435). The live suites assert the plan shapes and that every CSV count equals its streamed rows. (Findings F01, F02, F20, F21, F22.) - Data Six served indexes reach prod at the next publish through
scripts/sql/indexes.sql(98 expected): three partial(market, id)indexes ongm_investorfor thebuys_wholesale,buys_at_auctionandbuys_reofilters, and three trigram indexes ongm_parcelfor the parcel search’slegal.subdivision,owner.name_contains_wordsandfinancing.lender_name_containsfilters. Stage carries them since 2026-09-13. No contract change. (Findings F21, F26.) - Docs the Developer Agreement (Beta) and the Privacy notice are version
data-services-beta-v2026-09-13: Allowances are defined in credits with the overage ceiling and clause 3.3 says how a hard allowance and an overage plan behave; 1.1 lets an entity be identified by email within 10 Business Days of acceptance; 1.2 and Notice section 16 make the beta US-only; 11.1 bills the plan fee in advance and overage in arrears and, from later the same day, says the Free plan requires a payment method on file that is never charged on Free and is charged as 11.1 provides on a change to a paid plan (Robert: a card on file raises the cost of fake accounts), while the Notice’s Subscription and billing bullet says Stripe holds the payment method for every plan, the Free plan included; Notice section 13 gains the credit-ledger row (400 days after the last charged request) and section 10 is narrowed to investor, property and deal removals. Decided 2026-09-13 by Robert, by delegation to the session, not by counsel, whose read is still owed before the first outside key; in force under 21.1 on acceptance or 30 days after the email for every account that accepted v2026-09-11; the Free plan’s new shape is a 3.1 change and triggers no 11.4 notice. The Acceptable use and abuse policy’s rules are unchanged; its title block names the new version as the Agreement that incorporates it. No contract change. Terms and attribution - Changed API 0.11.0 (the section 7 proof of docs/data-services/PLAN-zuplo-pricing.md, 2026-09-13): a request the
credit ledger refuses at the line (403
quota_exceeded, before the query, by the charge script or on a legacytools/call) now writescredits_lineandcredits_period_endon its usage line, the line and the period the counter was checked against, with the four credits figures null (nothing was charged); the first build wrote all six null, so the period a request was refused against was only in the problem body’susedandline. No/v1contract change; the shape is in docs/god-mode-api/README.md and RUNBOOK.md, “Credits”.
- Changed API 0.11.0 (the section 7 proof of docs/data-services/PLAN-zuplo-pricing.md, 2026-09-12): a zero-priced
operation (
GET /v1/deals/summary,/deals/cells,/properties/resolve,/markets/{market}/changes,/investors/search,/dataset, and an uncharged MCP tool) no longer carriesX-Credits-Charged: 0through the gateway; it carries no credits header, as the public document has always declared. The twelve metered operations keep the header,0on a re-read and an empty page; an MCP tool result behind the gateway still carriesmeta.credits: { charged }either way. API reference
-
Added API 0.9.0: the credit ledger behind the
gateway lock. Every
/v1JSON 2xx served through the gateway carriesX-Credits-Charged: one credit per deal, investor or parcel the account receives for the first time plus the route’s base (2 onGET /v1/buyers/match), 0 on a re-read and on an empty page; none on the routes that charge nothing (summary, cells, resolve, the change series,GET /v1/investors/search,GET /v1/dataset); atools/callon/mcpcharges what its route charges. The count is exact per billing period against the line the gateway forwards (X-Gateway-Customer,X-Credits-Grant,X-Credits-Line,X-Credits-Period-End), a spent period answers 403quota_exceededwith the page unsent, and the Free plan’s first month cannot be bought twice by re-subscribing. New problem codes: 403plan_limit(the request is over what the plan allows: a radius over 5 miles or a viewport over 10 miles across on the Free and Starter plans, a county-wide or multi-ZIP parcel search there, an MCP page over the plan’s largest, the name-search day cap, withRetry-Afterto midnight UTC) and 503ledger_unavailablewithRetry-After: 5(the ledger is unreachable; nothing was charged, nothing was served). Behind the lock alimitabove the plan’s largest page (25 / 25 / 100 / 200 / 500) is lowered to it on REST,page.limitsays what was used, and refused by name on/mcp;GET /v1/markets/{market}/changesserves at most 52 weeks on the two small plans. The internal host is untouched. New settingCREDITS_MODE(ledger, orfreeto answer 0 and write nothing);PUBLIC_HOSTSnow requiresREDIS_URL, and the chart refuses the public Ingress with Redis off. 403quota_exceededand 403subscription_required, added below as the gateway’s own codes, are now also raised by the API itself behind the lock (producerboth;quota_exceedednow reads credits and names both stops), so the operators’openapi.jsonlists them under a/v1403 again,payment_overduebeing the one code it leaves out; and neitherX-Credits-ChargednorX-Rowsis ever on an error response, a 500 raised in the send included. Errors -
Added API 0.9.0:
GET /healthcarriescredits: { backend, mode, errors, steps }, the credit ledger’s backend, theCREDITS_MODElever and the failed operations and charge steps since start;GET /metricsgainsgateway_requests_total{route},credits_steps_total{kind},credits_charged_total{kind},credits_ledger_errors_total{op}andcredits_refusals_total{code}, every label value present from start and none of them a customer, plan or subscription. The usage line (O4) gainscustomer,credits,credits_new,credits_kindandcredits_mode. No/v1contract change; the alert rules and the reconciliation queries are in docs/god-mode-api/RUNBOOK.md, “Credits”. -
Added API 0.9.0: on
POST /mcpevery tool result served through the gateway carriesmeta.credits: { charged }, what the call cost: the credit ledger’s figure on a client negotiating protocol 2026-07-28 (which also receivesX-Credits-Charged), and on an initialize-era client the static weight the gateway meters (the base plus the asked page clamped to 50 and the plan’s largest; base plus one for a record; nothing on the coverage, summary and change-series tools), counted against the billing period before the call runs and refused at the line with 403quota_exceeded; such a client is billed per call, served or refused, and its re-reads are not free. Through the gatewaytools/listreturns fifteen tools: the five listing-agent and Investorlift-listing tools are served on the internal host alone, and a call to one there is an unknown-tool error.prompts/listthere returns five prompts:investorlift_listing_outcome, whose every step runs on an Investorlift-listing tool, is served on the internal host alone, andmarket_snapshotthere stops before its Investorlift summary step.api/mcp-manifest.public.jsonis that catalogue and those prompts and is what the site publishes; the server instructions, the concepts resource and every charged tool’s description say what a call costs, and behind the gateway the instructions are the public text, which says what is not served on that host and names no scope. Nothing changes on the internal host. The tools -
Added API 0.9.0: the problem code 403
payment_overdue, returned by the gateway at api.investorlift.com when a subscription’s last payment failed and the seven-day grace period has passed (the key is blocked until the card is updated under Manage Billing in the console; nothing is charged for a refused request), never by the API itself; in the catalogue and on every 403 of the public document so itstypeURL lands on the Errors page like every other code. Errors -
Added to the public document the response headers
X-Credits-Charged(set by the origin: the credits the response cost) andX-Credits-Remaining(set at the edge: the balance after it, absent on Enterprise), declared on every 2xx of the twelve metered operations (the deal, investor, buyer-match, property and parcel routes; not cells, summary, resolve, the change series, investor search or the dataset probe). API reference -
Changed API 0.9.0: the description of 403
quota_exceededand its What-to-do row on the Errors page speak of credits rather than requests and rows and name both stops: the plan’s credits for the billing period (the allowance, or on Growth and Scale the overage ceiling) and the Free plan’s lifetime credits (stopbudgetin the body); the body carriesusedandline. The code, the status and who raises it are unchanged. Errors -
Changed API 0.9.0 (the section 7 proof of docs/data-services/PLAN-zuplo-pricing.md): a
tools/callfrom an initialize-era MCP client whose static weight would pass the period’s line is refused 403quota_exceededbefore it is counted or metered; the first build refused only once the counter had reached the line, so a call one credit short was served and counted past it. Errors over MCP -
Added API 0.9.0 (the same proof): the usage line gains
credits_lineandcredits_period_end, the forwardedX-Credits-LineandX-Credits-Period-Endas the credit ledger’s period counter used them, so a period reconciles and a dispute is read from the line itself; null where no counter was read. No/v1contract change; the fields and the queries are in docs/god-mode-api/README.md and RUNBOOK.md, “Credits”. -
Changed API 0.9.0 (the same proof, row 9): a client that has left before the credit ledger step is not charged.
On
/v1and on a moderntools/call, a charge whose request the client abandoned during the query (a499in the origin’s log) no longer runs: nothing is written, noX-Credits-Chargedis set, the 499 usage line readscreditsnull, and the skipped step is counted under the new seriescredits_skipped_total{reason="client_gone"}; a legacytools/callthe client abandoned is not counted at its weight either. A client that leaves while the step is in flight gets a 499 line that waits for the step (at most one second) and carries what it took, the ledger drift the runbook reports; the first build could write that line before the step and charge behind it, leaving the origin’s counter one page ahead of the gateway’s meter. No/v1contract change; docs/god-mode-api/README.md section 7 and RUNBOOK.md, “Credits”. -
Docs (the same proof): Plans and limits, Errors and Errors over MCP say that a re-read is free while the plan has
credit, and that at the line of a hard plan every charged route answers 403
quota_exceeded, re-reads included, until the period resets or the plan changes, while the zero-priced routes and the MCP handshake keep answering; the API refuses before it prices the page, and the gateway’s own check refuses too. No contract change. Plans and limits -
Changed what a deals-only key receives on the four parcel payloads (
POST /v1/properties/search,GET /v1/properties/{property_id}/financing,/historyand/listing-history, and their MCP tools), served on the internal host since API 0.8.0: the contact-gated keys are left out of the object instead of carried as null, and the payloads no longer carrycontact_redacted. The keys are the search row’sowner.namesandowner.mailing, a mortgage’sborrowers, a lien’sparties, a listing cycle’sagent_nameandagent_license(the licence number joins the projection the same day: it is a contact field on the listing block and in the history payload already) and the person-naming columns of a history event’sbefore,afteranddeedpayloads. A client that testedcontact_redacted === trueorowner.names === nullon a deals-only key now tests for the missing key. A key with the contact scope receives every field as before, withcontact_redacted: false. The published examples, the MCP tool descriptions, the MCP instructions andmeta.contactfollow the same rule. Parcel search -
Removed from the public document (
openapi.public.json, the reference at developers.investorlift.com) on 2026-09-11, before the first outside key:GET /v1/deals/pins,GET /v1/agents/search,GET /v1/agents/{id},GET /v1/agents/{id}/listings,GET /v1/wholesale-listings,GET /v1/wholesale-listings/{id},GET /v1/investors/{id}/wholesale-purchases,GET /v1/wholesalers/search,GET /v1/wholesalers/{id}andGET /v1/wholesalers/{id}/listings, ten operations the developer tier never sells, with their tags, schemas and reference pages; the two codes only they raise, 422pin_cap_exceededand 422agents_unavailable, move to the Errors page’s partners section. The routes stay on the internal host and in the operators’openapi.json; the blocks the served rows carry (wholesale_listing,wholesale_purchases,wholesale_listings[], the listing agents) are unchanged.GET /v1/str-parcelsstays in the public document under a By contract tag, sold per jurisdiction under an order form. API reference -
Docs: Plans and limits is written around credits: what
counts (one credit per deal, investor or parcel the first time the account receives it, on any route; 0 on a re-read,
on cells, summary, resolve, the change series,
GET /v1/datasetand a sub-resource of a parcel already held; 2 plus 1 per new investor on the buyers match; the name search uncharged and capped per day), the five plans with their credits a month (Free’s first month included), what happens past the allowance (a stop, or on Growth and Scale an overage of up to three times the allowance), requests a minute and a day, the largest page, how wide a search may be on Free and Starter, the seven-day grace period and the two credit headers; no price is printed. Rate limits, the envelope, the spreadsheet walkthrough, Pricing, Your keys and the three MCP pages follow it:X-Rowsis the size of the body, atools/callcosts what its route costs on a client negotiating 2026-07-28 and a legacy client is billed per call, refused or served; Errors over MCP gainsplan_limit,quota_exceededandledger_unavailablerows. No contract change. Plans and limits -
Changed API 0.9.0:
meta.termson every/v1and/mcpresponse is now a short attribution in the form of the line the Developer Agreement requires developers to show beside the Data (“Data: Investorlift Data Services. Public-record and MLS listing data licensed through BatchData; …”) and still ends by pointing at the Terms and attribution page, which carries the full required line with the city credits and now the Investorlift Data Services Developer Agreement (Beta), versiondata-services-beta-v2026-09-11, beside the new Privacy notice and Acceptable use and abuse policy pages. Every page names support@investorlift.com for removal requests; the MCP concepts text points at the Privacy notice’s removal section instead, because no MCP surface may carry an email address. The link at the end of the sentence followsDOCS_URL, like the problemtypeURLs andGET /docs, instead of a hard-coded host (finding 5 of the PR #13 review). Terms and attribution -
Added API 0.9.0:
GET /v1/dataset, the freshness probe:dataset_version,data_end, the counties andloaded_atof every loaded market, plus the one-line version label, read from the market cache with no query; any key; carries anETagfor a304handshake. Read the dataset version -
Added the response headers
X-Rows(rows in the body),X-Dataset-VersionandX-Data-End(market=value, comma-joined) on every/v1JSON response, and in the headers a browser page may read;/mcpalready carried the last two. Coverage and freshness -
Added the problem codes 403
quota_exceededand 403subscription_required: returned by the gateway at api.investorlift.com when a plan’s monthly budget is spent or the key has no plan, never by the API itself, in the catalogue so theirtypeURLs land on the Errors page like every other code.unauthorizedandrate_limitedare now marked as sent by both the gateway and the API. The MCP resourcegodmode://problemscarries the whole catalogue, the two gateway codes included, and its description now says so and points at each code’sproducer. Errors -
Added
produceron every entry ofGET /docs/problemsand of thegodmode://problemsresource:origin,gatewayorboth, the fact the Errors page prints under each code, so a program can tell which side answers a code without reading its English. The operators’openapi.jsonno longer lists the two gateway codes under a/v1403 or shows their bodies, since the internal host never sends them; the public document does. Errors -
Added the settings
PUBLIC_HOSTS,GATEWAY_SECRETS,GATEWAY_KEY_NAMES(the gateway lock: on a public origin hostname every request without the gateway’s token is 404, the open routes are 404 with it, CSV is 406, and only a gateway key without a contact scope and withX-On-Behalf-Ofis served),ON_BEHALF_OF_RPM(the 240 a minute per key and user, now a setting),GATEWAY_ON_BEHALF_OF_RPM(1,800 behind the lock) andEDGE_REQUEST_ID_HEADER(zp-rid). Every one is empty or at today’s value by default, so the internal host behaves as before. AGATEWAY_KEY_NAMESentry with noAPI_KEYSrecord, or whose record carriescontactormcp_contact, stops the API at startup rather than refusing every request after the flip. -
Added a
usage: truelog line per/v1and/mcpresponse with the operation, the outcome, the key, the developer and, behind the gateway lock, the consumer, plan and subscription the gateway named: the origin’s side of the monthly reconciliation against the gateway’s metering ledger. No contract change. -
Changed
meta.termson every/v1and/mcpresponse now ends by pointing at the Terms and attribution page of the documentation site (https://developers.investorlift.com/guides/terms) instead of at a file path inside the API’s repository, which a reader of the public host cannot open. The attribution sentence before it is unchanged. Terms and attribution -
Docs the published
openapi.jsonis now the public tier’s document (api/openapi.public.json): one server,https://api.investorlift.com, the/v1routes alone, no header the gateway sets, no CSV, and none of the fields a deals-only key never sees; every runnable request on the site names that host; the machine-readable error catalogue isproblems.jsonon the site. Authentication -
Docs the site is written for a developer with a
zpka_key from the developer console: three landing pages (/get-a-key,/keys,/pricing) are the only pages that name the console, the navbar’s one button is “Get an API key”, and a new Plans and limits page states what a plan allows each month, each minute and per page, what counts as a request, the403 quota_exceededand429 rate_limitedit meets at a limit, fair use and support; no price is printed on the site, the console’s pricing page is the one place. Plans and limits -
Docs the
gm_keys, thecontactandmcp_contactscopes,X-On-Behalf-Of, the five internal rate-limit buckets and their per-response limit headers, the CSV export and the bring-your-own request id moved to a “Partners and staff” subsection at the foot of the page they were on (Authentication, Rate limits, Request ids, Give me a spreadsheet, Keys and headers, Errors over MCP); the public reader’s text above them describes the one host and the one key. The MCP pages namehttps://api.investorlift.com/mcpin every client entry and the Messages API connector as available. Authentication -
Docs the Errors page confines the four codes a
zpka_key never meets (on_behalf_of_required,scope_required,csv_cap_exceeded,not_ready) to its closing Partners and staff section, each marked as the internal host’s alone, andproblems.jsonon the site carries the public tier’s codes only, each with itsproducer;GET /docs/problemson the internal host still serves every code. Errors -
Docs the public document carries the parcel family of 0.8.0 (
POST /v1/properties/search, the financing, permits, history and listing-history sub-resources,GET /v1/markets/{market}/changes) as azpka_key receives it: without the owner’s names and mailing, the borrowers, the lien parties and the agent named on a listing cycle, and without the owner-name filter, which only a contact key may send. The search page’s request is generated from its JSON body and rendered against the public host like every other. Property search -
Added API 0.8.0: the parcel products over
data_services.gm_parceland its sidecars.POST /v1/properties/search(a location, thirteen filter groups, quicklists, the blocks named indatasets, keyset paging,count_only),GET /v1/properties/{property_id}/financing(the AVM with equity and LTV, every open lien, the recorded mortgage history and the involuntary liens),GET /v1/properties/{property_id}/permits(every building permit with the normalised tags and the per-parcel aggregates),GET /v1/properties/{property_id}/history(the week-by-week change timeline),GET /v1/properties/{property_id}/listing-history(the listing cycles) andGET /v1/markets/{market}/changes(the weekly change series of one ZIP). Borrower names on the financing route and the agent named on a listing cycle need thecontactscope, like owner names. The publish path carries the seventeen new tables since 2026-09-11 (22c,scripts/sql,publish-dataset.sh); they reach prod with the next stage-to-prod copy. Property search -
Added
meta.dated[]on the envelope andmeta.coverage[].parcel_as_of/meta.coverage[].parcel: the financing, lien and valuation blocks are a snapshot valued at 2026-06-25 (the Recorder & Mortgage and AVM datasets are not in the current BatchData delivery) and every response that carries one says so, onemeta.dated[]entry per block (valuation,financing,liens) with its as-of date;require_current: truerefuses with the new 422dated_refused. Dated data -
Added
contact_redactedonPropertyHistory: the event payloads’ contact columns (owner names, mailing address, listing agent names, licences and keys, the listing office, borrowers, lien parties) are null without the contact scope, on REST and ongodmode_property_history, and a contact key’s read is audited like the financing and listing-history routes. -
Added five problem codes: 422
parcels_unavailable, 422dated_refused, 422history_unavailable, 400quicklist_unavailable, 400dataset_unavailable. Errors -
Added six MCP tools over the same plans (
godmode_property_search,godmode_property_financing,godmode_property_permits,godmode_property_history,godmode_listing_history,godmode_market_changes): twenty tools in the catalogue. The MCPmeta.coverage[]row every tool carries keeps its ten fields; the parcel layer’sparcel_as_ofis read fromgodmode_coverageand thegodmode://marketsresource, not from every result. The financing, history and listing-history tools can carry contact fields under the same gate as the four existing single-record lookups. The twenty tools -
Changed
godmode_investors_nearbyconcise rows (the default format) no longer carry the contact block’s four identifying fields (contact.primary_address,contact.mailing_addresses,contact.person_members,contact.skip_trace_targets). A list tool never serves contact, so on this row they were always null; dropping them keeps the default page of 20 rows under the 20,480-byte concise budget (20,536 bytes at the Phoenix golden point on 2026-09-11). The seven contact counts and booleans,contact_redactedandmeta.contactare unchanged, and the detailed format still carries the four fields. The twenty tools - Docs the parcel search page’s request is generated and checked against the route’s body schema; the coverage, envelope, authentication and MCP overview pages and the MCP worked examples cover the parcel family. Property search
-
Data Phoenix (market
phx): the permit snapshot effective 2026-09-01 (2,377,484 permits on 663,041 Maricopa parcels), the financing slice valued at 2026-06-25, the Owner Profile block as of 2026-09-09, and the history lake over ZIP 85251 from the 2025-07-24 FULL onward (the counts are inpipeline/scottsdale-mvp/reports/build-log.md). -
Added API 0.7.0: every investor carries
auction, the foreclosure-auction and REO purchase block. OnInvestorListItem(so on every list row and match row) the nine-field summary: auction and REO purchase counts, the 24-month counts and the two active flags, the last purchase days, the resale share. OnInvestorProfilethe full block: the kinds bought at (TRUSTEE_SALE,SHERIFF_SALE, and the shown-but-not-countedHOA_TRUSTEE_SALE,TAX_SALE,TAX_LIEN_DEED), the Texas venue (Auction.com, ServiceLink, Xome), the outcomes at 12 months, the median discount to the later resale, the REO grantor classes. Null where the market’s counts are not published. The Investor object -
Added
bought_auction_kindon everyDeal: the auction kind of the purchase deed when it was a third-party auction purchase, null otherwise; and the cellBought at auctionon the deals CSV and the investor history CSV. The investors CSV and the match CSV gain seven auction / REO cells. None is a contact column. The Deal object -
Added the shared filters
buys_at_auctionandbuys_reo(investor level) andbought_auction_kind(deal level), and the problem code 422auction_unavailable: a market whose registry predates the counts answers it to the three filters, andmeta.coverage[].auction_counted(also onGET /health) says so in advance. Filters -
Added MCP:
godmode_deals_summary,godmode_deals_search,godmode_investors_nearbyandgodmode_buyers_matchtake the three filters (the two booleans as JSON booleans); every coverage entry carriesauction_counted. The concise deal row omitsbought_auction_kindand the concise investor list row omitsinvestor.auction(the detailed format carries both). No resource or prompt was added or removed. The MCP endpoint -
Data Phoenix and Houston rebuilt on stage on 2026-09-11 with the auction and REO counts (registry runs 9 and 5;
auction_countedtrue for both). Built on the branch as 0.5.0 on 2026-09-10 and renumbered at the rebase onto main after the short-term rental release (0.5.0) and the MCP release (0.6.0). Coverage and freshness -
Changed API 0.6.2: every problem body’s
typeURL now points at the Errors page of the documentation site, anchored at the code (https://developers.investorlift.com/guides/concepts/errors#invalid_cursor), instead of atGET /docs/problemson the API host. Thecodeyou switch on is unchanged,GET /docs/problemsstill serves the machine-readable catalogue, and its entries carry the same new URLs. Errors -
Changed every operation description in
openapi.jsonis now one paragraph, capped at 600 characters and checked in CI. The depth moved to a page per endpoint on the documentation site, linked from the description. API reference -
Added one example error body per code on every problem response in
openapi.json, so the reference shows a real 400 and a real 429 beside the 200. API reference - Docs the reference is organised objects first: one page per object (Deal, Investor, Property, Listing agent, Investorlift listing, Wholesaler, Short-term rental parcel, the response envelope), then the endpoints that return them. Objects
- Docs new Quickstart: get a key, make one call, read the answer. Quickstart
-
Docs every request on the site is now runnable in curl, Node and Python, generated from
openapi.jsonbynpm run docs:syncand checked in CI, so a renamed parameter cannot survive in a guide. Quickstart - Docs “Calling the API” is split into one page per concept: authentication and scopes, the response envelope, pagination, filters, coverage and freshness, rate limits, errors, request ids and versioning. The old URLs redirect. Core concepts
-
Docs the Errors page carries one anchored section per code, with the body it produces, what causes it and what to
do. It is what every error’s
typeURL now opens. Errors -
Docs pages can be copied as Markdown or opened in Claude, ChatGPT and Cursor from the menu on every page, and the
site serves its own documentation search server at
/mcpbeside the data server on the API host. The MCP endpoint -
Docs five corrections an audit of every reference page against the schemas found: an error body carries eight
fields,
errors?included; a wholesaler’sn_noneis the NONE verdict, which is wider than “no closing deed”, and that verdict has four cases, two of which do have a recorded deed;chain_certainsits in the deed block; and an agent profile’sn_self_listingscounts HIGH-confidence investor links only. Investorlift listings - Docs a page no longer restates a page size or a cap that the parameter table on the same page already shows, and a number the code owns is named where it lives rather than copied. API reference
-
Changed every published error example now carries the path of the operation it sits under. Examples were keyed by
HTTP status alone, so the 503 shown on a deals call named
/readyas itsinstanceand the 422 on agent search named/v1/deals/pins; 529 of the 587 published bodies named another route.not_readyis no longer offered under any route but the readiness probe. API reference - Changed the codes listed in each problem response’s description are derived from the catalogue rather than typed out a second time, so a new code reaches the reference with its example, its section and its status description together. Errors
- Added API 0.6.0:
POST /mcp, a Model Context Protocol server on the same host, with the same bearer key,X-On-Behalf-Ofrule, rate limits and request line as/v1(the 2026-07-28 revision through the official TypeScript SDK v2, stateless;GETandDELETEanswer 405, anyOriginheader 403, batched JSON-RPC arrays 400; hidden from/docsandopenapi.json). Fourteen read-only tools, four resources, six prompts. The MCP endpoint - Added the scope value
mcp_contact: contact fields travel over MCP only on single-record tools, only for a key carrying it, and only when itsX-On-Behalf-Ofnames a person. Metered by a new bucket of 30 per minute per (key, user). A key carrying either contact scope must send the header. Keys and headers - Added
recovery, one plain sentence per problem code, onGET /docs/problemsand appended to every MCP error result. Errors - Added
meta.contacton every MCP tool result:not_on_lists,redacted_no_scope,servedornone_on_record. The MCP endpoint - Changed the request line gains
mcp_method,tool_name,outcomeandresponse_formaton/mcp, with new metricsmcp_requests_total,mcp_request_duration_seconds,mcp_contact_served_totalandrate_limit_rejections_total{bucket="mcp_contact"}. The in-flight slot, the request line and thehttp_*metrics now also cover a client that disconnects after its request body was consumed, through the response’scloseevent. - Changed the query part of every
/v1handler moved into the plan functions ofapi/src/v1/plans/, shared with the MCP tools. No wire change, andopenapi.jsonis unchanged by the move. - Added API 0.6.1:
GET /docsanswers 302 toDOCS_URL(new setting) instead of serving Scalar, which is removed.GET /openapi.jsonandGET /docs/problemsare unchanged. - Added the setting
CORS_ORIGINS: the browser origins allowed on the read routes, none by default./mcpstays closed to any browser origin. - Docs the rendered reference moved to a documentation site built from
docs/site/and published at https://developers.investorlift.com. New scriptnpm run docs:syncwrites its generated pages; CI checks them likeopenapi.jsonand validates the site. Introduction - Docs
openapi.jsonlists production and stage inservers[], hides the four ops probes from the site, names its tag groups, writes every path template in inline code, and publishes the recorded examples in the deals-only shape with every contact-scope field nulled.
- Added API 0.5.0: the cities’ short-term rental licence and permit rolls. New route
GET /v1/str-parcels(geometry,str_statusdefault LICENSED,PENDING,owner_attributed,business_use,has_investor,operator,jurisdiction, sort, cursor, CSV). List short-term rental parcels - Added
short_term_rentalon every Property and on every hold Deal row,stronInvestorProfile,InvestorListRowandBuyerMatch, andstr_as_ofwithstr.jurisdictionsonmeta.coverage[]and/health. All null where the tables are not published. The short-term rental parcel - Added the filters
str_statuson/v1/deals, its summary and CSV and on/v1/investors/{id}/deals, andstr_operatorandstr_holdings_minin the shared filters. New problem code 422str_unavailable. Filters in plain words - Added
str_business_useon the same deal lists: hold rows whose licence is a business use attributed to the current owner, or the rest withfalse. Measured over LICENSED and PENDING records unlessstr_statussays otherwise. - Changed the owner identity rule of
GET /v1/properties/{property_id}(decision D2): names and mailing are also served when the parcel carries a business-use licence attributed to the owner by a date or a name, never by assumption. Thenot_an_investor_holdreason and the RENTAL_SIGNAL holding-kind description are reworded accordingly. Read one parcel - Added five short-term rental cells to the deal CSVs and two to the investors CSV. None is a contact column.
- Changed
str_statusaccepts LICENSED, PENDING and EXPIRED only; NONE stays on every hold row’s block but is not filterable in this release (400 for the value). No query shape made its predicate over the 868k-rowgm_str_coveragecold-safe on stage: 17.6 s at best against the 10 s statement timeout. A build-time column ongm_dealis the fix for a later release. - Added API 0.4.0: every Investorlift listing is served as a wholesale transaction judged by the county deeds. New
routes
GET /v1/wholesale-listings,GET /v1/wholesale-listings/{id},GET /v1/wholesalers/search,GET /v1/wholesalers/{id},GET /v1/wholesalers/{id}/listingsandGET /v1/investors/{id}/wholesale-purchases; new id kindswl_<32 hex>andwsr_<12 hex>. List Investorlift listings - Added
wholesale_listingon every Deal,wholesale_purchasesonInvestorProfile,InvestorListRowandBuyerMatch,wholesale_listings[]on Property, andwholesale_as_ofonmeta.coverage[]and/health. All null where the tables are not published. The Investorlift listing - Added the filters
bought_on_investorlift=trueon/v1/dealsandsource=investorlifton/v1/investors/{id}/deals. New problem code 422wholesale_unavailable. - Changed
meta.termsnow reads “public records and Investorlift marketplace records”. Terms and attribution
Removed
- Removed
candidate_class,tierandsub_tierfrom the fullDealobject on/v1/deals,/v1/deals/{id},/v1/investors/{id}/dealsand thedeals[]of the two property routes. They were classifier diagnostics marked “safe to ignore” in the schema and nothing read them;kindcarries the meaning. The trimmedsample_deals[]on a match row and the CSV exports never carried them. Part of the served-column cut, commit c466b50. The Deal object