Skip to main content
Investorlift Data Services (the God Mode API). This log has one entry per /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:
AddedChangedDataDocs
API 0.33.1, 0.33.0, 0.32.0, 0.31.0
  • Changed API 0.33.1: the build kind 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 read other again. Every build description names the rule a market follows (meta.coverage[].loaded_at), n_lot_to_build_built is described against n_builds, and docs/god-mode-api/PLAN-build-kind.md section 1 defines the bounds. Deal
  • Changed API 0.33.0: a POST /v1/properties/search with a zip list and no point, bbox or property_id chooses 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 is 422 outside_coverage, and the problem names it in zips_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 tool godmode_property_search follows 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_measured is 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. LenderInvestorRef gains display_name. That stub sits behind top_borrowers[].investor and top_investors[].investor on a lender profile (GET /v1/lenders/{lender_id}) and behind investor on a borrower row (GET /v1/lenders/{lender_id}/borrowers). A loan row’s deal block gains investor_display_name beside investor_name (GET /v1/lenders/{lender_id}/loans), null exactly when investor_name is 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 with investor_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.ts with the stage beta. The planned list is empty. No contract change.
  • Added API 0.31.0: the four investor stubs that carried name alone 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_name and intermediate.display_name on an Investorlift wholesale listing (GET /v1/wholesale/listings), top_buyers[].display_name on a wholesaler (GET /v1/wholesalers/{wholesaler_id}), investor_links[].investor_display_name on an agent (GET /v1/agents/{agent_id}) and holder.display_name on 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-17 and 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’s display_name field 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.mjs holds the pages to the rules as a step of pnpm verify and of CI; docs/god-mode-api/DOCS-STE.md is the rule sheet.
  • Added API 0.30.0: GET /v1/coverage answers which data is available where. For every loaded market it returns one row per dataset a market can lack. The dataset keys are deeds, listings, agents, wholesale, auction, str, parcels, financing, permits, owner_profile, history, cash_sale, lenders and addresses. Each row carries one status: served, partial, not_measured or not_published. Beside it are the dated and stale flags, the as_of date, and the gate a call meets where the API does not serve the dataset. The row also carries the granularity and the scope at that grain, and the market row carries label and stage (Houston reads beta).
  • 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 lat with lng, county (a FIPS or a name), zip (through the Census ZIP-to-county crosswalk) and market. With one of them the answer is the markets that cover the place, with a place_status per dataset. A place nothing covers answers 200 with markets: [] and place.status outside, never 422. 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, like GET /v1/dataset. Over /mcp, godmode_coverage runs the same plan, with the place inputs (latitude, longitude, county, zip, market) and response_format. Its rows carry label, stage, datasets (the matrix), str_cities and history_zips beside the fields they always carried. data.place and data.planned ride beside data.markets, and the concise format caps the jurisdiction, issuer and ZIP lists at ten. godmode://coverage and godmode://markets/{market} serve the same rows, and the property-history tool now names history_zips.
  • Changed API 0.30.0: meta.coverage[].lenders gains dated and n_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:record writes api/coverage.snapshot.json and npm run docs:sync renders 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.json is 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 to meta.coverage[] or the Coverage pages.
  • Docs A test fails the next coverage fact typed by hand. The coverage guide no longer describes GET /health as reachable from the public host.
  • Added API 0.29.0: display_name on every investor object, the name to print beside name, 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’s seller and end_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 equals name; it is served wherever name is and is null exactly when name is 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. name keeps its value and is now described as the deed spelling, SURNAME GIVEN for a person: match, join and search on name, print display_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 without X-On-Behalf-Of; every other /v1 path and /mcp still answer 400 on_behalf_of_required without 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_measured says 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 of POST /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_use follows 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 one metro market with the default 20-mile point tolerance, 1,321,876 parcels, data_end 2026-08-26 (King 2026-08-25, Pierce 2026-08-26, Snohomish 2026-08-25), registry run 2, dataset_version 1789595959 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’s Public Action deed as TAX_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 is SELL_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) and meta.coverage[].str explains 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 200/200 / 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 for sea: agents (no Washington licence roll reaches a commercial requester; 422 agents_unavailable). The plan, the measurements and the research are docs/seattle/PLAN.md and docs/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 the metro description name the Seattle metro (the coverage tool lists the markets from the database and needed no change), every state description of a market row (the coverage row, GET /v1/dataset, the investor objects) says AZ, TX or WA, the last_sale_price, prior_sale_price and last_sale_price_per_sqft null 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.terms is 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}/financing and GET /v1/properties/{property_id}/permits at 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) and GET /v1/properties/{property_id}/listing-history at a Scottsdale condominium of the history lake’s ZIP half a mile from the point, which the golden parcel sits outside of, and GET /v1/markets/{market}/changes for ZIP 85251 at the default 52 weeks. The comps route is the one documented /v1 operation still without an example. No contract change.
  • Added API 0.27.0 (provisional): over /mcp, meta.credits on every tool result behind the gateway carries used, line and period_end beside charged: 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), the X-Credits-Line the gateway forwarded and the period end it resets on. godmode_coverage carries an account block beside data.markets[]: plan, tier and period_end; credits with grant, line, used, remaining (grant minus used, negative on Growth and Scale into the overage down to grant minus line, and on a hard line by at most the last page’s cost; the API’s exact counter, which differs from X-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 lifetime budget (line, spent, remaining); limits with largest_limit, the small plans’ geometry bound, max_weeks and name_search (day_cap, used_today, resets_in_seconds); requests (per_minute, per_day, a copy of the gateway’s catalogue); and counters, which says whether the counts were read (read, off, unavailable). Off the gateway the block reads plan null and tier internal with no credits. A standing read the ledger did not answer is counted under credits_ledger_errors_total{op="standing"}, a new label apart from read, 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_min and holds_max join the shared filters: the investors whose market-wide investor.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 example holds_min=20 for the larger portfolios, holds_max=0 for the investors holding nothing, and buyers without an investor id pass unless registered_only=true. They apply wherever the shared filters do (GET /v1/investors, GET /v1/buyers/match, GET /v1/deals with 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_desc on GET /v1/investors and godmode_investors_nearby orders the rows by in_radius.n_holds, the holdings inside the geometry, as every sort of that list orders by what happened there. GET /v1/investors/{id} gains median_purchase_year: the median year the investor bought the parcels it still holds, a whole year computed over the n_holds rows when the profile is read, null when it holds nothing or no holding is dated; godmode_investor_lookup carries 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_channel on GET /v1/deals/summary and, through the same plan, on godmode_deals_summary: five filtered counts over the summary’s row set, each on the deal row’s own channel field, from_wholesaler (bought_from_wholesaler true), from_investor (bought_from_investor true: the seller carried an investor id), distressed (bought_distressed true), auction (bought_auction_kind set) and other_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 to n_deals, and n_deals minus other_or_unknown is the rows in at least one. other_or_unknown is not a count of purchases from a homeowner, because a contract assignment leaves no deed. auction is null, never 0, where any market the location covers has meta.coverage[].auction_counted false, the rule the bought_auction_kind filter 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 recorded getDealSummary example 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 /v1 problem response no longer carries the dataset freshness headers ETag, Last-Modified, X-Dataset-Version and X-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-cache stays on a problem, as before. A compressed export (Accept-Encoding: gzip) whose stream fails after the count now answers its problem without the stream’s X-Row-Cap, Content-Disposition and Content-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.headers and referenced from the responses that carry it (The response envelope). Every response of the operators’ document declares X-Request-Id; every /v1 response the limiter counted, errors included, the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset trio (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 /v1 2xx and the 304 ETag, X-Dataset-Version and X-Data-End, and every /v1 2xx X-Rows; Retry-After on every 429, on the 503 of every /v1 route and on the 403 of GET /v1/investors/search (the name-search day cap); X-Row-Cap on the 200 and the 422 of the routes that export CSV (on the public document, the loans of a lender alone); and GET /v1/dataset declares its 304 with the freshness headers it repeats. The public document describes X-Request-Id as the edge sends it (also zp-rid; absent on a refusal the gateway answers itself) and moves its two credit headers under the same components.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_test and the agent’s role on a listing, deal_type, holding_kind, ownership_confirmed, the property owner block, n_active, n_pending and n_listed on the investor’s listings block, the listing_status filter, among others) now name the listing data end (meta.coverage[].listings_data_end) or the deed data end (meta.coverage[].data_end), the way days_on_market does; n_active, n_pending, n_on_market, n_listed_12m and n_sold_12m on an agent’s listings block, n_active on an agent search hit and the holder of an agent’s listing row name the listing slice the agent registry was built from (meta.coverage[].agents_data_end, equal to listings_data_end while 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 wholesale PENDING and NO_DEED_120 outcomes, the permit and history filters and summaries of the property search, the w_wholesale_buyer and w_activity weights of the buyer match, str_status, a lender’s churn_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, an as_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-argument age() or the 'now' and 'today' input strings (test/v1-sql-clock.test.ts). The coverage tool’s description says what contact_via_mcp means without naming the scope or the header behind it, and says its stale flag is the one value it serves that is measured against the request’s day. No contract change.
  • Added API 0.24.0: page.capped on the four name searches, GET /v1/investors/search, GET /v1/agents/search, GET /v1/wholesalers/search and GET /v1/lenders/search: true when more names matched than limit allowed and the page was cut at it, false when every hit is on the page, so a page holding exactly limit hits 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 tools godmode_investor_lookup, godmode_agent_lookup, godmode_wholesaler_lookup and godmode_lender_lookup carry the same capped beside candidates on an unresolved name (item 27 of the SFR Analytics API review of 2026-09-15).
  • Changed API 0.24.0: on GET /v1/investors/search the matched member of a many-member investor is the one with the most purchase deeds (a trigram hit: the most similar spelling, then the most deeds), where it was the first matching key alphabetically; n_deals_min and include_public apply inside every search step, so a broad query that came back with fewer than limit rows 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. On GET /v1/lenders/search the 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 and page.capped is 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.geometry beside meta.reference_point on 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/search and, when a geometry, a zip or a city was given, GET /v1/lenders and GET /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_miles 2 on the GET lists when none was sent and 1 around property_id on the parcel search; a property_id request echoed as kind: "radius" around the parcel centroid with property_id filled; a viewport as the [west, south, east, north] tuple; a ZIP list under zip as sent and a city as the folded upper-case value that was compared; lat and lng the reference point that rode beside a viewport or a place, null without one. On the parcel search a ZIP-only body echoes kind: "place" and a county-only body echoes nothing; a zip or county_fips list beside a geometry is applied as sent and not echoed. The comps echo the subject as kind: "radius" around its centroid with property_id the subject and radius_miles the 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 a zip or a city as kind: "place"; a county is not echoed: the echo describes the shape asked. REST only: the MCP tools’ meta carries reference_point alone. GET /v1/markets/{market}/changes carries weeks_applied on data beside first_week and last_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_changes refuses weeks above 52 on Free and Starter as 403 plan_limit naming the cap (limit: "weeks", cap: 52, errors[0].param weeks), as every paged tool refuses a limit over the plan’s page, instead of clamping the series in silence; REST keeps the clamp and reports it in weeks_applied. The tool’s description and its weeks input say so, and the plan_limit catalogue row names the change series’ weeks. Read a ZIP’s weekly changes
  • Docs the changes page said first_week and last_week were 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 are meta.coverage[].parcel.history.first_week / last_week. The two field descriptions, the page and its gotchas say so, and the envelope pages describe meta.geometry. Read a ZIP’s weekly changes
  • Added API 0.22.3: the MCP prompt underwrite_property, the seventh beside find_buyers and market_snapshot (item 23 of the SFR Analytics API review of 2026-09-15). For one house at a price (asking_price, then property: a prop_ id, a latitude,longitude pair or a street address with its ZIP or city after it) it walks the model through godmode_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’s data_end, read off the coverage), godmode_property_search (count_only over 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’s godmode_coverage row carries the lender registry and its lenders.counties lists the parcel’s county, godmode_lenders_top and godmode_lender_loans for the hard-money lenders active in the ZIP; it names the figures to carry into the underwrite, the meta.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 /v1 change. Prompts and chaining
  • Docs API 0.22.2: corrections from the SFR Analytics API review of 2026-09-15 (items 30 and 31). The DealKind description 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), the kind legend, by_kind.wholesale, both n_wholesales fields and n_quick_resales state the wholesale bound in the same words, so both OpenAPI documents move, and the document carries externalDocs pointing at this site. On the site, The ideas you need carries the same sentence under the deal kinds, and the deal object page’s wholesale row 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-parcels per 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) and godmode_investors_nearby with the same inputs as the alternatives; bought_after as an absolute date the reader computes from meta.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_events as the trailing count beside the row’s market-wide n_deals, n_purchase_events_12m and days_since_last_purchase; the operator rule (rows sharing operator_id are 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/investors gains 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/search around a point, or around the parcel GET /v1/properties/resolve?address answers for a street address, with the active-listing, new-construction and corporate-owned quicklists, the listing and valuation blocks and sort: 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’s year_built, owner.kind for the builder and the dated valuation; what the list is not (Phoenix alone today, with Houston answering parcels_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 with filters.owner.name_contains_words. The godmode_property_search description names the same question (“new-construction homes for sale within a radius”) among what it answers and no longer restates the dataset list its datasets field 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-listings and GET /v1/str-parcels. zip is a comma list or repeated key of 5-digit ZIPs (up to 50); city is the parcel’s postal city as the county records it, any case (folded upper case; never the short-term rental jurisdiction of meta.coverage[].str.jurisdictions[]). One place per request, and not beside radius_miles, bbox or property_id (400 geometry_conflict); lat + lng may ride along as the reference point only, so distance_miles, nearest_distance_miles and sort=distance keep their rule and meta.reference_point echoes the point, while a place with no point takes the list’s default order (sort=distance there is 400 sort_requires_point); no shape is echoed. A ZIP or a city no parcel of a loaded market carries is 422 outside_coverage naming it (zips_unknown or city in 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 403 plan_limit naming 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/match takes no place: zip or city there is 400 geometry_conflict, because the match ranks near a point. The MCP tools godmode_deals_search, godmode_deals_summary, godmode_investors_nearby and godmode_wholesale_listings take zip and city under the same rules and godmode_buyers_match refuses 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/resolve takes a street address: address (the street line as the county records it) with zip or city and an optional unit, exclusive with lat/lng and apn. 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 in unit), otherwise 422 ambiguous_address lists up to 25 candidates[] with truncated. ResolveCandidate gains unit. meta.coverage[] (and /health, godmode_coverage, godmode://markets) gains address_as_of, the deed data end the address table was read from, null where it is not published; an address is then 422 addresses_unavailable. godmode_property_lookup takes the same form (address, zip, city, unit; an ambiguous address is resolved: false with the units as candidates), and the concepts resource points a model from an address to a property_id. Two error codes (43), both bound to the resolve operation in the OpenAPI document along with ambiguous_apn, which was declared on every 422 route before.
  • Added API 0.20.0: GET /v1/properties/{property_id}/comps and the MCP tool godmode_property_comps: the comparable sales around one parcel, most similar first. Every priced sale inside radius_miles (default 1, at most 5) and the last days_back days before the market’s data_end (default 180, at most 1,095) on a parcel within beds_tolerance (1), sqft_pct (20) and year_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, with days_on_market and list_price); source picks deed, mls (422 listings_unavailable where none) or both (the default; the MLS record wins over a deed recorded within 45 days of it). Each comp carries distance_miles, price_per_sqft, the building facts, cash_sale_proxy and last_sale_lender from 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, and similarity (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). summary carries the counts by source, the medians, an indicative price_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 beside 24m (a stored rank over 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/lenders takes every period class inside a ZIP, city or county (market-wide 24m and a calendar month, which reads the month kind; 400 validation_error code period_not_served otherwise) and a geometry (lat + lng + radius_miles, bbox or property_id, the shared fields; not beside a place, 400 geography_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_borrowers and n_deals_financed null there, not measured over an arbitrary set of cells; in_geometry {res, n_cells, n_cells_lent} on every row; outside every market 422 outside_coverage, in another market 400 geometry_market); the ranking row carries share_by_volume and n_borrowers. Two routes, zero-priced: GET /v1/lenders/{id}/rankings (group_by month, zip, city, county or cell with res 8 or 7, period, min_n 10 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, and partial on the slice month and year, floor on a year before history_floor_year) and GET /v1/lenders/{id}/cells (res, a window period, product[] narrowing n; 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 carries takebacks (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_share null under 20 captured loans, median_days_takeback_to_resale, is_auction_lender read 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 while takebacks_measured is false) and rankings gains share_by_volume_24m, n_zips_ranked_first_24m, n_cities_ranked_first_24m and n_counties_ranked_first_24m (the lender’s own 10 or more and rank 1; the county count null while counties_measured is false); the loan row carries foreclosed (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) and GET /v1/lenders/{id}/loans takes foreclosed, 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 carries other_lenders[] (the stubs behind other_lender_ids[], which stays); the wholesale listing carries lenders_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 in meta.dated[]; meta.coverage[].lenders carries takebacks_measured and counties_measured. Over MCP godmode_lenders_nearby joins the catalogue on both hosts (the ranked lenders inside a geometry, the investors_nearby shape; /rankings and /cells are not tools, the pins and cells precedent) and godmode_lenders_top takes period: twenty-five tools; the catalogue budget took its third 8 KiB raise, to 172,032 (166,431 bytes measured, the arithmetic in test/mcp-registry.test.ts); the wholesale tools’ concise row leaves lenders_active to the detailed format, the loans tool’s foreclosed and the borrowers tool’s other_lenders likewise. Every phase-5 field is null and every phase-5 parameter and route 422 lenders_unavailable on a registry built before the place rankings or a host without the h3 extension (MarketCache.hasLenderPlaces; scripts/sql/roles.sql creates h3 and h3_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.sql stays 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: outcome on a loan tied to a deal reads FORECLOSED where 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 read RESOLVED_BY_RESALE before): on stage 231 captured loans’ outcome moved against the phase-4 set: 40 to FORECLOSED (30 from RESOLVED_BY_RESALE, 7 from OPEN, 3 from REFINANCED), and 191 more left RESOLVED_BY_RESALE once a distressed deed stopped counting as the resale (151 to REFINANCED, 23 to UNKNOWN, 17 to OPEN); months_to_resolution is then measured to the take-back deed, by_outcome_24m on 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[] and by_zip[] on the profile count dated instruments alone in n_all, now that the geography table’s all period is the dated ones (an instrument without a recording date is counted in the profile’s n_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_serve and public_copy stages 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_unlinked 6,168 of 6,586 (418 linked to a loan row of the lender; 126 served identities read is_auction_lender) market-wide): 32,046 identities served, lender registry run 16, dataset_version 1789530735 (the market row restamped 1789532610).
AddedChangedDataDocs
API 0.18.0, 0.17.0, 0.16.0, 0.15.0, 0.14.0
  • Changed API 0.18.0: cash_sale_proxy on 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 the cash-buyer quicklist and the sale.cash_sale filter 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 422 cash_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 code cash_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, reason and lender_run, the run that retired the id): an id retired by a merge redirects to the current lender with meta.resolved_from on GET /v1/lenders/{id}, GET /v1/lenders/{id}/loans and GET /v1/lenders/{id}/borrowers and 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 is 410 gone (superseded_by null, 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 as markets[] on the profile (never market_required from the walk; the list routes keep it). The two lender-id filters walk it too: financed_by on GET /v1/investors, GET /v1/deals (and /pins, /cells, /summary) and GET /v1/buyers/match, and filters.financing.lender_id on POST /v1/properties/search, bind the current id behind a merged one and stamp meta.resolved_from after any investor_id chain, answer 410 gone for a retired id and, as the investor_id filter does, 404 not_found for an id no registry run ever issued (until now the id was bound as sent and matched nothing). The profile carries class_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, kind ACQUISITION or SUBSIDIARY; 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) and id_history[] (old_id, reason, lender_run: the ids merged into the lender, oldest run first, each still answering with a redirect); class_basis and nmls ride 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_from admits a len_ id beside the investor and agent ids, and 410 gone names the three kinds. Over MCP the lookup forwards meta.resolved_from, its lender_id input 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 in test/mcp-registry.test.ts). On stage the build retired 118 ids with alias rows (120 rows in gm_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.sql forty-four tables, indexes.sql 124, assert_no_contact.sql forty-three COLUMNS rows); 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_lender and gm_lender_loan moved 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 as lender_name null with lender_class null 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) and parcel.financing.n_lenders no 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; the private-lender-financed quicklist 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 DEV len_992e364eeaeb among 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_6ac531f2070d and len_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_lookup carries meta.dated[] (the lenders and financing stamps) on a detailed lookup whose deals[] rows carry the financing block of 0.16.0, as GET /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 gives financing null 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_lender suppression row, assert_no_contact.sql and check-no-person-names.mjs over 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}/loans carries purpose (PURCHASE when a priced deed on the parcel is dated in the 45 days up to the recording, NOT_PURCHASE when the registry covers those days and none falls in them, UNKNOWN otherwise; decided by the deeds, never by the file’s label), deed (dt, price, loan_to_price) on a PURCHASE row, deal (the investor deal the loan is tied to: deal_id, kind, link_window STRICT or LOOSE, is_purchase_loan, secondary_loans_n, the investor, the prices, gross_profit, hold_days), outcome (RESOLVED_BY_RESALE, REFINANCED, OPEN, UNKNOWN; FORECLOSED reserved for a later release) with months_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 on purpose, outcome, deal_kind and investor_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, ten top_borrowers never carrying a person’s name, ten competing_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, ten top_investors, is_investor_lender) and flips_financed (the flips whose purchase loan was the lender’s in the 36 months to 45 days before recordings_through, capture_share, n_flips_by_known_borrowers, share_of_financed_flips, the resale margins median_gross_profit with p25 and p75, median_spread_ratio, median_hold_days, median_loan_to_price, null under 20 resold flips with both prices, by_year), beside by_purpose_24m, by_outcome_24m, median_months_to_payoff, terms.loan_to_price, rankings.rank_purchase_24m, the flat counts n_purchase_money_24m, n_investor_loans_24m, n_borrowers, n_borrowers_24m and is_investor_lender, by_year[]’s n_purchase and n_investor, by_month[]’s n_purchase_money, and borrowers_measured beside the three measured flags; is_hard_money is 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_basis BEHAVIOUR or BOTH, 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 the contact scope with party_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) and n_deals_financed; period (24m, all), q (a company name; persons never match), registered_only, churned, investor_kind, min_loans, sorts loans_desc, volume_desc, last_loan_desc, churned_first; the audit line under the contact scope as on the loans route. GET /v1/lenders takes purpose=purchase (refused beside position or product), investor_only and sort=purchase_desc, and the ranking row carries n_purchase_money, n_investor and n_deals_financed. The investor profile (GET /v1/investors/{id}) carries financing (n_loans, n_loans_12m, n_loans_24m, n_lenders, ten lenders_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 carries financing (purchase_loan_status FOUND, NONE_CAPTURED or NOT_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_CAPTURED is 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-buyer keeps reading the sale). GET /v1/investors, GET /v1/buyers/match and GET /v1/deals take financed_by=len_... and uses_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 the financing reason (w_financing, weight 0 by default: the investor borrows from a lender that also lends in the subject’s ZIP). Every one of them answers 422 lenders_unavailable where 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 MCP godmode_lender_borrowers joins the catalogue on both hosts: twenty-four tools; a list tool, so a person’s name and every party_key are null on its rows and meta.contact reads not_on_lists; the loans tool keeps purpose, deed, deal, outcome, investor_id in 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 leaves financing to 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 in test/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 table gm_lender_borrower rides the parcel family’s publish path (22c seq 47, rules 1514, 1532 to 1534 and 1591 to 1597, indexes.sql 122); 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}/loans answers Accept: text/csv: every filtered row as lender-<id>-loans.csv in the requested sort, limit and cursor ignored, the set counted before the first row streams, at most 50,000 rows (422 csv_cap_exceeded above it, a public code from today on this operation alone), at 0 credits; on api.investorlift.com from Growth (403 plan_limit below it) with recorded_from required (400 validation_error, code window_required, without it; recorded_to stays optional) from the gateway release that forwards Accept on this route (until it ships the public host answers the JSON page whatever Accept says; this release is the origin’s half), on the internal host with neither gate. Every loan row carries borrowers[] under the contact scope: a RECORDED row’s borrowers as the recorded history names them (an OPEN row reads null), with contact_redacted false; 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_id and mortgage_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/search takes filters.financing.lender_id beside lender_name_contains, the parcels whose last recorded mortgage’s lender carries the id, every spelling at once, over a new gm_parcel index (400 validation_error when no market of the location has a registry). GET /v1/lenders serves position=1 with product, the first liens of the named products from precomputed counts, which 0.14.0 refused as not_precomputed. Over MCP godmode_lender_loans joins the catalogue on both hosts: twenty-three tools; a list tool, so the borrower keys are left out of its rows and meta.contact reads not_on_lists. None of it is priced, and the early-access sentence of 0.14.0 still applies. The two gm_parcel columns 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-financed quicklist 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 or last_mortgage_hard_money after 30a wrote the flag; 7,555 parcels carry it). cash-buyer is unchanged: it reads the sale, never the history. Quicklists
  • Changed API 0.15.0: amount_min and amount_max on GET /v1/lenders/{id}/loans accept any non-negative integer; the 2,000,000,000 ceiling of 0.14.0 is gone (a value above it answered 400 validation_error before). 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 stable len_ 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, with hard_money, position, product and sort=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_year and by_month against the market, the product, term, label and property-type mix, terms with their coverage, geography, coverage, rankings and a summary, 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, which n_amount_excluded_24m counts and amount_outlier marks on the loan row) and GET /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, beside parcel.financing.n_lenders, which counts raw spellings and now says so) and the lenders block of meta.dated[]; 422 lenders_unavailable where the registry is not published, 422 outside_coverage for 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 unless include_persons=true; nothing carries a lender’s phone or email. Over MCP godmode_lender_lookup and godmode_lenders_top join the catalogue on both hosts: twenty-two tools. None of it is priced: no credits are charged, the responses carry no X-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, the gm_lender takedown 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 URBAN and a third truncation) and 24 rarer HUD forms read lender_class GOVERNMENT, 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. The private-lender-financed quicklist (43,623 to 30,666 parcels on stage) and the financing.lender_class filter 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 of invalid_id, not_found and outside_coverage now name the lender list and the len_ id kind beside the kinds they already named; the codes and statuses are unchanged. Errors
  • Docs the position of 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_date on 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
AddedChangedData
API 0.13.0, 0.11.0
  • 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} and GET /v1/wholesalers/{id}/listings, with the Listing agent, Investorlift listing and Wholesaler objects, their reference pages and the two walkthroughs; 422 agents_unavailable rejoins 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 the investorlift_listing_outcome prompt are served at api.investorlift.com, and market_snapshot keeps its Investorlift step there: twenty tools and six prompts on both hosts. None of it is priced: no credits are charged, the responses carry no X-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/pins stays 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 were other; a client that switches on kind should treat a value it does not know as other. On the stage tables of 2026-09-14 that is 10,006 of Phoenix’s 89,129 other deals and 10,030 of Houston’s 42,160, served since the publish of 2026-09-17. The classifier label deal_kind gains BUILD. A purchase of a builder’s finished home that is later resold stays other. kind filters accept build; meta.kinds on 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.build and a per-year build count on GET /v1/deals/summary, n_builds on GET /v1/deals/cells, and n_builds on GET /v1/investors/{id}. Each reads 0 for a market whose tables predate the kind. Deals summary
  • Data Production serves the refreshed Phoenix market: phx data_end 2026-08-27 (was 2026-08-12), registry_run 10, dataset_version 1789409315; hou unchanged at data_end 2026-08-11 (registry_run 5, re-stamped 1789408252 by the swap). Published from the stage RDS with scripts/copy-gm-from-stage.sh (37 tables, a 1.72 GB dump, 1,188 s end to end; the six served indexes of scripts/sql/indexes.sql and the float8 gm_score of functions.sql reached prod with it). API 0.11.0 was released to stage and to production the same day (build log, 2026-09-14).
AddedChangedDataDocs
API 0.10.0, 0.11.0
  • 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_DESCRIPTIONS beside the filter table in api/src/schemas/common.ts); the REST reference keeps the full texts and openapi.json is byte-identical. tools/list is 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/cells and the godmode_deals_summary tool, 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_MB sizes it (default 32, 0 disables). A cached answer’s meta.generated_at is the time it was served. GET /health gains result_cache; /metrics gains result_cache_lookups_total{outcome}, result_cache_bytes and result_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 503 database_unavailable and 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 schema core exists 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 (modes no-swap, swap-only, full, reload; the dump parked in S3 between the two halves; gzip level 1 through the new DUMP_COMPRESS setting of publish-dataset.sh, whose laptop path and --from-dump rollback 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/search sort id (the default without a point or property_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 400 invalid_cursor with “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); the str_status, str_business_use, bought_on_investorlift, source=investorlift, buys_wholesale, buys_at_auction and buys_reo filters are hashed sets over the markets in play instead of per-row probes; nullable sorts render nulls last natively 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 on gm_investor for the buys_wholesale, buys_at_auction and buys_reo filters, and three trigram indexes on gm_parcel for the parcel search’s legal.subdivision, owner.name_contains_words and financing.lender_name_contains filters. 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 legacy tools/call) now writes credits_line and credits_period_end on 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’s used and line. No /v1 contract change; the shape is in docs/god-mode-api/README.md and RUNBOOK.md, “Credits”.
Changed
API 0.11.0
  • 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 carries X-Credits-Charged: 0 through the gateway; it carries no credits header, as the public document has always declared. The twelve metered operations keep the header, 0 on a re-read and an empty page; an MCP tool result behind the gateway still carries meta.credits: { charged } either way. API reference
AddedChangedRemovedDataDocs
API 0.9.0, 0.8.0, 0.7.0, 0.6.2
  • Added API 0.9.0: the credit ledger behind the gateway lock. Every /v1 JSON 2xx served through the gateway carries X-Credits-Charged: one credit per deal, investor or parcel the account receives for the first time plus the route’s base (2 on GET /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); a tools/call on /mcp charges 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 403 quota_exceeded with the page unsent, and the Free plan’s first month cannot be bought twice by re-subscribing. New problem codes: 403 plan_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, with Retry-After to midnight UTC) and 503 ledger_unavailable with Retry-After: 5 (the ledger is unreachable; nothing was charged, nothing was served). Behind the lock a limit above the plan’s largest page (25 / 25 / 100 / 200 / 500) is lowered to it on REST, page.limit says what was used, and refused by name on /mcp; GET /v1/markets/{market}/changes serves at most 52 weeks on the two small plans. The internal host is untouched. New setting CREDITS_MODE (ledger, or free to answer 0 and write nothing); PUBLIC_HOSTS now requires REDIS_URL, and the chart refuses the public Ingress with Redis off. 403 quota_exceeded and 403 subscription_required, added below as the gateway’s own codes, are now also raised by the API itself behind the lock (producer both; quota_exceeded now reads credits and names both stops), so the operators’ openapi.json lists them under a /v1 403 again, payment_overdue being the one code it leaves out; and neither X-Credits-Charged nor X-Rows is ever on an error response, a 500 raised in the send included. Errors
  • Added API 0.9.0: GET /health carries credits: { backend, mode, errors, steps }, the credit ledger’s backend, the CREDITS_MODE lever and the failed operations and charge steps since start; GET /metrics gains gateway_requests_total{route}, credits_steps_total{kind}, credits_charged_total{kind}, credits_ledger_errors_total{op} and credits_refusals_total{code}, every label value present from start and none of them a customer, plan or subscription. The usage line (O4) gains customer, credits, credits_new, credits_kind and credits_mode. No /v1 contract change; the alert rules and the reconciliation queries are in docs/god-mode-api/RUNBOOK.md, “Credits”.
  • Added API 0.9.0: on POST /mcp every tool result served through the gateway carries meta.credits: { charged }, what the call cost: the credit ledger’s figure on a client negotiating protocol 2026-07-28 (which also receives X-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 403 quota_exceeded; such a client is billed per call, served or refused, and its re-reads are not free. Through the gateway tools/list returns 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/list there returns five prompts: investorlift_listing_outcome, whose every step runs on an Investorlift-listing tool, is served on the internal host alone, and market_snapshot there stops before its Investorlift summary step. api/mcp-manifest.public.json is 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 its type URL 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) and X-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_exceeded and 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 (stop budget in the body); the body carries used and line. 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/call from an initialize-era MCP client whose static weight would pass the period’s line is refused 403 quota_exceeded before 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_line and credits_period_end, the forwarded X-Credits-Line and X-Credits-Period-End as 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 /v1 contract 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 /v1 and on a modern tools/call, a charge whose request the client abandoned during the query (a 499 in the origin’s log) no longer runs: nothing is written, no X-Credits-Charged is set, the 499 usage line reads credits null, and the skipped step is counted under the new series credits_skipped_total{reason="client_gone"}; a legacy tools/call the 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 /v1 contract 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, /history and /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 carry contact_redacted. The keys are the search row’s owner.names and owner.mailing, a mortgage’s borrowers, a lien’s parties, a listing cycle’s agent_name and agent_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’s before, after and deed payloads. A client that tested contact_redacted === true or owner.names === null on a deals-only key now tests for the missing key. A key with the contact scope receives every field as before, with contact_redacted: false. The published examples, the MCP tool descriptions, the MCP instructions and meta.contact follow 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} and GET /v1/wholesalers/{id}/listings, ten operations the developer tier never sells, with their tags, schemas and reference pages; the two codes only they raise, 422 pin_cap_exceeded and 422 agents_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-parcels stays 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/dataset and 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-Rows is the size of the body, a tools/call costs 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 gains plan_limit, quota_exceeded and ledger_unavailable rows. No contract change. Plans and limits
  • Changed API 0.9.0: meta.terms on every /v1 and /mcp response 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), version data-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 follows DOCS_URL, like the problem type URLs and GET /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 and loaded_at of every loaded market, plus the one-line version label, read from the market cache with no query; any key; carries an ETag for a 304 handshake. Read the dataset version
  • Added the response headers X-Rows (rows in the body), X-Dataset-Version and X-Data-End (market=value, comma-joined) on every /v1 JSON response, and in the headers a browser page may read; /mcp already carried the last two. Coverage and freshness
  • Added the problem codes 403 quota_exceeded and 403 subscription_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 their type URLs land on the Errors page like every other code. unauthorized and rate_limited are now marked as sent by both the gateway and the API. The MCP resource godmode://problems carries the whole catalogue, the two gateway codes included, and its description now says so and points at each code’s producer. Errors
  • Added producer on every entry of GET /docs/problems and of the godmode://problems resource: origin, gateway or both, 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.json no longer lists the two gateway codes under a /v1 403 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 with X-On-Behalf-Of is 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) and EDGE_REQUEST_ID_HEADER (zp-rid). Every one is empty or at today’s value by default, so the internal host behaves as before. A GATEWAY_KEY_NAMES entry with no API_KEYS record, or whose record carries contact or mcp_contact, stops the API at startup rather than refusing every request after the flip.
  • Added a usage: true log line per /v1 and /mcp response 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.terms on every /v1 and /mcp response 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.json is now the public tier’s document (api/openapi.public.json): one server, https://api.investorlift.com, the /v1 routes 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 is problems.json on 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, the 403 quota_exceeded and 429 rate_limited it 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, the contact and mcp_contact scopes, 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 name https://api.investorlift.com/mcp in 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, and problems.json on the site carries the public tier’s codes only, each with its producer; GET /docs/problems on 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 a zpka_ 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_parcel and its sidecars. POST /v1/properties/search (a location, thirteen filter groups, quicklists, the blocks named in datasets, 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) and GET /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 the contact scope, 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 and meta.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, one meta.dated[] entry per block (valuation, financing, liens) with its as-of date; require_current: true refuses with the new 422 dated_refused. Dated data
  • Added contact_redacted on PropertyHistory: 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 on godmode_property_history, and a contact key’s read is audited like the financing and listing-history routes.
  • Added five problem codes: 422 parcels_unavailable, 422 dated_refused, 422 history_unavailable, 400 quicklist_unavailable, 400 dataset_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 MCP meta.coverage[] row every tool carries keeps its ten fields; the parcel layer’s parcel_as_of is read from godmode_coverage and the godmode://markets resource, 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_nearby concise 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_redacted and meta.contact are 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 in pipeline/scottsdale-mvp/reports/build-log.md).
  • Added API 0.7.0: every investor carries auction, the foreclosure-auction and REO purchase block. On InvestorListItem (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. On InvestorProfile the full block: the kinds bought at (TRUSTEE_SALE, SHERIFF_SALE, and the shown-but-not-counted HOA_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_kind on every Deal: the auction kind of the purchase deed when it was a third-party auction purchase, null otherwise; and the cell Bought at auction on 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_auction and buys_reo (investor level) and bought_auction_kind (deal level), and the problem code 422 auction_unavailable: a market whose registry predates the counts answers it to the three filters, and meta.coverage[].auction_counted (also on GET /health) says so in advance. Filters
  • Added MCP: godmode_deals_summary, godmode_deals_search, godmode_investors_nearby and godmode_buyers_match take the three filters (the two booleans as JSON booleans); every coverage entry carries auction_counted. The concise deal row omits bought_auction_kind and the concise investor list row omits investor.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_counted true 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 type URL 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 at GET /docs/problems on the API host. The code you switch on is unchanged, GET /docs/problems still serves the machine-readable catalogue, and its entries carry the same new URLs. Errors
  • Changed every operation description in openapi.json is 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.json by npm run docs:sync and 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 type URL 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 /mcp beside 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’s n_none is 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_certain sits in the deed block; and an agent profile’s n_self_listings counts 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 /ready as its instance and the 422 on agent search named /v1/deals/pins; 529 of the 587 published bodies named another route. not_ready is 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
AddedChangedDocs
API 0.6.0, 0.6.1
  • Added API 0.6.0: POST /mcp, a Model Context Protocol server on the same host, with the same bearer key, X-On-Behalf-Of rule, rate limits and request line as /v1 (the 2026-07-28 revision through the official TypeScript SDK v2, stateless; GET and DELETE answer 405, any Origin header 403, batched JSON-RPC arrays 400; hidden from /docs and openapi.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 its X-On-Behalf-Of names 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, on GET /docs/problems and appended to every MCP error result. Errors
  • Added meta.contact on every MCP tool result: not_on_lists, redacted_no_scope, served or none_on_record. The MCP endpoint
  • Changed the request line gains mcp_method, tool_name, outcome and response_format on /mcp, with new metrics mcp_requests_total, mcp_request_duration_seconds, mcp_contact_served_total and rate_limit_rejections_total{bucket="mcp_contact"}. The in-flight slot, the request line and the http_* metrics now also cover a client that disconnects after its request body was consumed, through the response’s close event.
  • Changed the query part of every /v1 handler moved into the plan functions of api/src/v1/plans/, shared with the MCP tools. No wire change, and openapi.json is unchanged by the move.
  • Added API 0.6.1: GET /docs answers 302 to DOCS_URL (new setting) instead of serving Scalar, which is removed. GET /openapi.json and GET /docs/problems are unchanged.
  • Added the setting CORS_ORIGINS: the browser origins allowed on the read routes, none by default. /mcp stays 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 script npm run docs:sync writes its generated pages; CI checks them like openapi.json and validates the site. Introduction
  • Docs openapi.json lists production and stage in servers[], 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.
AddedChanged
API 0.5.0, 0.4.0
  • Added API 0.5.0: the cities’ short-term rental licence and permit rolls. New route GET /v1/str-parcels (geometry, str_status default LICENSED,PENDING, owner_attributed, business_use, has_investor, operator, jurisdiction, sort, cursor, CSV). List short-term rental parcels
  • Added short_term_rental on every Property and on every hold Deal row, str on InvestorProfile, InvestorListRow and BuyerMatch, and str_as_of with str.jurisdictions on meta.coverage[] and /health. All null where the tables are not published. The short-term rental parcel
  • Added the filters str_status on /v1/deals, its summary and CSV and on /v1/investors/{id}/deals, and str_operator and str_holdings_min in the shared filters. New problem code 422 str_unavailable. Filters in plain words
  • Added str_business_use on the same deal lists: hold rows whose licence is a business use attributed to the current owner, or the rest with false. Measured over LICENSED and PENDING records unless str_status says 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. The not_an_investor_hold reason 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_status accepts 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-row gm_str_coverage cold-safe on stage: 17.6 s at best against the 10 s statement timeout. A build-time column on gm_deal is 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}/listings and GET /v1/investors/{id}/wholesale-purchases; new id kinds wl_<32 hex> and wsr_<12 hex>. List Investorlift listings
  • Added wholesale_listing on every Deal, wholesale_purchases on InvestorProfile, InvestorListRow and BuyerMatch, wholesale_listings[] on Property, and wholesale_as_of on meta.coverage[] and /health. All null where the tables are not published. The Investorlift listing
  • Added the filters bought_on_investorlift=true on /v1/deals and source=investorlift on /v1/investors/{id}/deals. New problem code 422 wholesale_unavailable.
  • Changed meta.terms now reads “public records and Investorlift marketplace records”. Terms and attribution
Removed
  • Removed candidate_class, tier and sub_tier from the full Deal object on /v1/deals, /v1/deals/{id}, /v1/investors/{id}/deals and the deals[] of the two property routes. They were classifier diagnostics marked “safe to ignore” in the schema and nothing read them; kind carries the meaning. The trimmed sample_deals[] on a match row and the CSV exports never carried them. Part of the served-column cut, commit c466b50. The Deal object