# Mainview API Real estate platform API for agents and agentic LLMs. Base URL: `https://api.mainview.dev` ## Discovery and Docs - `GET /docs` -- This overview. - `GET /api/auth-service/whoami` -- Your identity + which accountIds your PAT can act on. - `GET /docs/services` -- List of services - `GET /docs/services/{slug}` -- One service's methods + model pointer. - `GET /docs/methods/{name}` -- One method's input params + examples. - `GET /docs/models/{name}` -- Full field schema for a model. Skip unless you need field-level detail. - `GET /docs/concepts/listings` -- MLS listings vs the agent's own custom listings. - `GET /docs/concepts/contacts-crm` -- How to record what an agent says about a contact. Read before writing contacts. - `GET /docs/concepts/professional-sites` -- How to build out an agent's website: names, roster, assets, and what reaches real people. Read before writing sites or professionals. - Prefer MCP? Same surface over JSON-RPC: `https://mcp.mainview.dev/docs`. ## Auth - `X-API-Key: mvx_` (preferred for agents) - `Authorization: Bearer mvx_` also accepted. - Issued by an account owner from the Mainview dashboard. Long-lived, scope-gated, tied to one user and one account membership. - JWT is for browser sessions only โ€” use a PAT for agent work. โ†’ Full guide: `/docs/auth` ## URL Pattern `/api/-service/[/{id}]` - `GET` `get-*` -- Fetch one record by id. - `GET` `list-*` -- List with filters, search, and paging. - `POST` `create-*` -- Create a record. - `PATCH` `update-*` -- Partial-merge update. - `PUT` `replace-*` -- Full replacement. - `POST` `trash-*` -- Soft-delete. Reversible. - `POST` `restore-*` -- Restore from trash. - `DELETE` `delete-*` -- Permanent destroy. Custom reads: `GET`. Custom actions: `POST`. Account-scoped services are always scoped to your token's account. To act on a different account, use a token issued for that account. Null values stripped for PAT/Bearer callers. Override: `?omitNulls=false`. โ†’ Filtering params: `/docs/filtering` ยท Sorting syntax: `/docs/sorting` ## Response Envelopes - List: `{ data: [...], meta: { fetchedCount, limit, offset, totalCount? } }` - Single: `{ data: { ... } }` - Error: `{ error: { code, message } }` ## Services - `/docs/services/address-service` -- Normalized street addresses with geocoding. - `/docs/services/asset-service` -- Metadata for files uploaded to S3. _(account-scoped)_ - `/docs/services/auth-service` -- Identity, session, and account discovery for the authenticated caller - `/docs/services/business-service` -- Public-realm businesses, mostly home builders. _(account-scoped)_ - `/docs/services/city-service` -- Public-realm cities with lat/lng points, polygons, and descriptions. - `/docs/services/contact-service` -- People in an agent's CRM: buyers, sellers, leads, past clients, sphere, and industry partners. Also covers household, company, and location records via entityType. Most CRM facts are columns on the contact (consent, standing classification, buying and selling intent, cadence, email program enrollment); hand-curated groupings are lists applied via tag-assignment-service. Booleans are nullable, where null means never established rather than false. The server enforces contradictions between fields on every update, so read the response back. Read /docs/concepts/contacts-crm before writing contacts. _(account-scoped)_ - `/docs/services/contact-tag-assignment-service` -- Puts one contact on one contact tag (a LIST in owner-facing copy). Deleting either the contact or the tag removes the assignment automatically. _(account-scoped)_ - `/docs/services/contact-tag-service` -- Hand-curated groupings of an agent's contacts, which owner-facing copy calls LISTS. Reach for one only when no contact column expresses the idea; a fact the platform automates on belongs in a column instead. See /docs/concepts/contacts-crm. _(account-scoped)_ - `/docs/services/county-service` -- Public-realm counties with boundaries. - `/docs/services/custom-listing-service` -- A real estate agent's own custom listings. Used on their site and marketing material. Account-scoped. Writable. Not to be confused with MLS-backed listings / listing-service. _(account-scoped)_ - `/docs/services/deal-service` -- Real estate transactions an agent is working. _(account-scoped)_ - `/docs/services/email-service` -- Transactional email send via SES _(account-scoped)_ - `/docs/services/listing-service` -- MLS listings -- the canonical real-estate listing surface (read-only mirror of MLS data, ~2M+ records). Use this for any "find homes in X", comp, or market-data query. Not to be confused with agent-owned account-scoped custom-listing. - `/docs/services/open-house-service` -- Scheduled open-house events sourced from MLS data for MLS listings. _(account-scoped)_ - `/docs/services/page-asset-assignment-service` -- Puts an uploaded image or video into a named MEDIA SLOT on one page -- the hero image behind a page title, the body images inside its sections. Keyed by page + slot. Site-wide chrome (logos, favicon) is a different service: site-asset-assignment-service. _(account-scoped)_ - `/docs/services/page-block-service` -- Structured content blocks (sections, steps, checklist items, FAQs, press mentions) that make up a DB-driven page body. _(account-scoped)_ - `/docs/services/page-service` -- Editable CMS pages within a site. Each page belongs to one site (siteId) and has a page role (home, about, contact, listings, places, post, custom, etc.). Account-scoped via the parent site. _(account-scoped)_ - `/docs/services/place-service` -- Geographic places, primarily neighborhoods (isNeighborhood) and condo buildings (isCondoProject). Also developments, subdivisions, etc. Use placeId to scope listing queries to a specific place, or skip the lookup and pass the name straight to list-listings via placeNameQ. Places do not include cities, postal codes, or counties. _(account-scoped)_ - `/docs/services/postal-code-service` -- ZIP codes with boundaries. - `/docs/services/professional-service` -- Primary record for public-facing agent/broker profiles with bios, brokerage info, site copy, license data, and internal data. _(account-scoped)_ - `/docs/services/property-sale-service` -- Sale records for properties. - `/docs/services/real-estate-agent-service` -- Licensed real estate agents and brokers normalized from MLS member feeds. - `/docs/services/redirect-service` -- URL redirects for sites. _(account-scoped)_ - `/docs/services/saved-search-service` -- Stored filters for saved listing searches. _(account-scoped)_ - `/docs/services/school-district-service` -- Boundaries for property and city association - `/docs/services/site-asset-assignment-service` -- Wires an uploaded asset into a named piece of SITE CHROME -- header logo, footer logo, favicon, social share image. One row per role per site. Uploading a logo through asset-service does nothing visible until a row here gives it a role. Page-specific hero and body imagery is a different service: page-asset-assignment-service. _(account-scoped)_ - `/docs/services/site-domain-service` -- Hostnames routed to a site: the authoritative host-to-site routing table. One site can have several records (production domain, stage alias). The Mainview platform subdomain is derived from the canonical sites.slug and is NOT stored here. Account-scoped. _(account-scoped)_ - `/docs/services/site-footer-service` -- Footer config for sites. _(account-scoped)_ - `/docs/services/site-professional-assignment-service` -- Puts a professional ON a site: the roster row that decides whether a person appears on the team page, the about page, and the contact page, in what order, and whether leads route to them. A professional record on its own renders NOWHERE until it has one of these. Creating one also auto-mints that person's vanity profile path (their name, slugified) in uri-route-service. _(account-scoped)_ - `/docs/services/site-service` -- A tenant-owned website served by the platform: one site = one brand + one or more domains. Account-scoped. Pages live in page-service. Hostnames routed to a site (production + stage) live in site-domain-service. _(account-scoped)_ - `/docs/services/site-testimonial-assignment-service` -- Places a testimonial ON a site and decides which pages it can appear on. A testimonial created through testimonial-service renders NOWHERE until it has one of these. The site nav hides the /testimonials page entirely when nothing is placed there. _(account-scoped)_ - `/docs/services/state-service` -- US states and territories. - `/docs/services/submission-service` -- Form submissions from site visitors. _(account-scoped)_ - `/docs/services/tag-assignment-service` -- Joins tags to resources like listings, contacts, and places by user and account. _(account-scoped)_ - `/docs/services/tag-service` -- User-defined tags, typically applied to listings, contacts, and places. _(account-scoped)_ - `/docs/services/testimonial-service` -- Testimonials real estate agents can request, create, and post on their site. _(account-scoped)_ Full list + all methods per service: `/docs/services/` ## Models - `/docs/models/address` -- `Address` - `/docs/models/asset` -- `Asset` - `/docs/models/business` -- `Business` - `/docs/models/city` -- `City` - `/docs/models/contact` -- `Contact` - `/docs/models/contacttag` -- `ContactTag` - `/docs/models/contacttagassignment` -- `ContactTagAssignment` - `/docs/models/county` -- `County` - `/docs/models/customlisting` -- `CustomListing` - `/docs/models/deal` -- `Deal` - `/docs/models/listing` -- `Listing` - `/docs/models/openhouse` -- `OpenHouse` - `/docs/models/page` -- `Page` - `/docs/models/pageassetassignment` -- `PageAssetAssignment` - `/docs/models/pageblock` -- `PageBlock` - `/docs/models/place` -- `Place` - `/docs/models/postalcode` -- `PostalCode` - `/docs/models/professional` -- `Professional` - `/docs/models/propertysale` -- `PropertySale` - `/docs/models/realestateagent` -- `RealEstateAgent` - `/docs/models/realestateoffice` -- `RealEstateOffice` - `/docs/models/redirect` -- `Redirect` - `/docs/models/savedsearch` -- `SavedSearch` - `/docs/models/schooldistrict` -- `SchoolDistrict` - `/docs/models/site` -- `Site` - `/docs/models/siteassetassignment` -- `SiteAssetAssignment` - `/docs/models/sitedomain` -- `SiteDomain` - `/docs/models/sitefooter` -- `SiteFooter` - `/docs/models/siteprofessionalassignment` -- `SiteProfessionalAssignment` - `/docs/models/sitetestimonialassignment` -- `SiteTestimonialAssignment` - `/docs/models/state` -- `State` - `/docs/models/submission` -- `Submission` - `/docs/models/tag` -- `Tag` - `/docs/models/tagassignment` -- `TagAssignment` - `/docs/models/testimonial` -- `Testimonial` Full list with service links: `/docs/models/` ## Pitfalls - list-listings does NOT accept a generic q param -- there is no full-text remarks search. To search by place or location name, pass placeNameQ (a neighborhood/building/complex name) or locationNameQ (any location string -- the server figures out place vs city vs county vs ZIP) and the server resolves it for you. Otherwise use geography filters (cityName, postalCode, countyName) or addressQ for street fragments. - For "homes in " (e.g. "homes in Madison Park"), the one-shot is GET /api/listing-service/list-listings?placeNameQ=Madison Park -- no need to call list-places first. The resolved place is echoed in meta.resolvedLocation; reuse its id (placeId) on later calls to skip resolution. Use placeId directly (resolve once via place-service.list-places) only when you need to disambiguate among multiple matches yourself. - For sales status filtering on list-listings, use salesStatusesAny -- a comma-separated list of sales_status tokens (e.g. for_sale, pending, closed). "pending" is broad and matches every pending sub-status. The preset boolean flags (forSale, pending, closed, comingSoon, contingent) exist but salesStatusesAny is cleaner. - "Listings" means MLS listings (listing-service, millions of records). The user's own custom listings are a separate resource (custom-listing-service). When a user says "find homes" or "comps" they mean listing-service. When they say "my listings" or "listings on my site" they mean custom-listing-service. - meta.totalCount is opt-in -- pass includeTotal=true to compute it. Without it, meta.totalCount is absent (saves a COUNT query on large tables). - sort accepts snake_case with _asc/_desc suffix (sort=created_at_desc), camelCase (sort=createdAt defaults to asc), or split params (sortBy=createdAt&sortDir=desc). All three are equivalent. ## Liveness `GET /health` -- `{ data: { status: "ok", platform: "api" } }` -- No auth.