Discover/ATTOM Data API
live

ATTOM Data APIapi.developer.attomdata.com

Access ATTOM property details, owner records, neighborhood POI, and sales trend data via 5 structured endpoints covering addresses, geographies, and more.

Endpoints
5
Updated
2mo ago

What is the ATTOM Data API?

The ATTOM Data API exposes 5 endpoints covering property search, detailed property attributes, owner records, neighborhood points of interest, and geographic sales trends. get_property_detail_owner returns current ownership data alongside full property records, while get_transaction_sales_trend aggregates yearly or monthly transaction metrics for a given geography — useful for market analysis pipelines that need structured, address-level real estate data.

Try it
Page number
Search radius in miles
Street address line 1
City, state, zip
Latitude for radius search
Results per page
Longitude for radius search
Postal/ZIP code to search
Property type filter
api.parse.bot/scraper/7d4d00ce-c23d-4935-a089-995a4e9497d6/<endpoint>
Ready to send
Fill in the parameters and hit sign in to send to see live response data here.
Call it over HTTPgrab a free API key at signup
curl -X GET 'https://api.parse.bot/scraper/7d4d00ce-c23d-4935-a089-995a4e9497d6/get_property_address?page=1&pagesize=2&postalcode=90210' \
  -H 'X-API-Key: $PARSE_API_KEY'
Python SDK · recommended

Typed, relational, agent-ready

A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.

  • Fully typed · autocompletes
  • Objects link to objects
  • Typed errors & pagination

Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:

uv add parse-sdk
uv run parse init
uv run parse add --marketplace api-developer-attomdata-com-api

uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.

"""
ATTOM Data API Client

A Python client for accessing comprehensive property data, ownership information,
and sales trends through the ATTOM Data API.

Get your API key from: https://parse.bot/settings
"""

import os
import requests
from typing import Any, Dict, List, Optional


class ParseClient:
    """
    Client for interacting with the ATTOM Data API through Parse Bot.
    
    Attributes:
        base_url: The base URL for the API
        scraper_id: The unique scraper identifier
        api_key: The API key for authentication
    """
    
    def __init__(self, api_key: Optional[str] = None):
        """
        Initialize the ParseClient.
        
        Args:
            api_key: API key for authentication. If not provided, reads from PARSE_API_KEY env var.
        """
        self.base_url = "https://api.parse.bot"
        self.scraper_id = "7d4d00ce-c23d-4935-a089-995a4e9497d6"
        self.api_key = api_key or os.getenv("PARSE_API_KEY")
        
        if not self.api_key:
            raise ValueError("API key must be provided or set in PARSE_API_KEY environment variable")
    
    def _call(self, endpoint: str, method: str = "POST", **params) -> Dict[str, Any]:
        """
        Make an API call to the Parse Bot scraper.
        
        Args:
            endpoint: The endpoint name (e.g., 'get_property_address')
            method: HTTP method ('GET' or 'POST')
            **params: Parameters to send with the request
            
        Returns:
            Response JSON as a dictionary
            
        Raises:
            requests.RequestException: If the API call fails
        """
        url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
        headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json"
        }
        
        try:
            if method.upper() == "GET":
                response = requests.get(url, headers=headers, params=params)
            elif method.upper() == "POST":
                response = requests.post(url, headers=headers, json=params)
            else:
                raise ValueError(f"Unsupported HTTP method: {method}")
            
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            print(f"API call failed: {e}")
            raise
    
    def get_property_address(
        self,
        address1: Optional[str] = None,
        address2: Optional[str] = None,
        postalcode: Optional[str] = None,
        latitude: Optional[float] = None,
        longitude: Optional[float] = None,
        radius: Optional[float] = None,
        propertytype: Optional[str] = None,
        page: int = 1,
        pagesize: int = 100
    ) -> Dict[str, Any]:
        """
        Search for properties within a specified area or matching address criteria.
        
        Args:
            address1: First line of property address
            address2: Second line (city, state, zip)
            postalcode: ZIP code to search
            latitude: Latitude for radius search
            longitude: Longitude for radius search
            radius: Search radius in miles
            propertytype: Property type filter
            page: Page number for pagination (default: 1)
            pagesize: Number of results per page (default: 100)
            
        Returns:
            Response containing property array and status
        """
        params = {
            k: v for k, v in {
                "address1": address1,
                "address2": address2,
                "postalcode": postalcode,
                "latitude": latitude,
                "longitude": longitude,
                "radius": radius,
                "propertytype": propertytype,
                "page": page,
                "pagesize": pagesize
            }.items() if v is not None
        }
        
        return self._call("get_property_address", method="GET", **params)
    
    def get_property_detail(
        self,
        attomid: Optional[str] = None,
        address1: Optional[str] = None,
        address2: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Get rich property details for a specific property.
        
        Args:
            attomid: ATTOM unique property identifier
            address1: First line of address
            address2: Second line of address (city, state, zip)
            
        Returns:
            Response containing detailed property information
        """
        params = {
            k: v for k, v in {
                "attomid": attomid,
                "address1": address1,
                "address2": address2
            }.items() if v is not None
        }
        
        return self._call("get_property_detail", method="GET", **params)
    
    def get_property_detail_owner(self, attomid: str) -> Dict[str, Any]:
        """
        Get detailed property data including current owner information.
        
        Args:
            attomid: ATTOM unique property identifier (required)
            
        Returns:
            Response containing property details with owner information
        """
        return self._call("get_property_detail_owner", method="GET", attomid=attomid)
    
    def get_transaction_sales_trend(
        self,
        geoidv4: str,
        startyear: int,
        endyear: int,
        interval: str = "yearly",
        propertytype: Optional[str] = None,
        page: int = 1,
        pagesize: int = 100
    ) -> Dict[str, Any]:
        """
        Get sales trends for a given geography.
        
        Args:
            geoidv4: Geographic V4 identifier (required, 32-char hex hash)
            startyear: Start year for trend analysis (required)
            endyear: End year for trend analysis (required)
            interval: Trend interval - 'yearly' or 'monthly' (default: 'yearly')
            propertytype: Property type filter
            page: Page number (default: 1)
            pagesize: Results per page (default: 100)
            
        Returns:
            Response containing sales trends array
        """
        params = {
            k: v for k, v in {
                "geoidv4": geoidv4,
                "startyear": startyear,
                "endyear": endyear,
                "interval": interval,
                "propertytype": propertytype,
                "page": page,
                "pagesize": pagesize
            }.items() if v is not None
        }
        
        return self._call("get_transaction_sales_trend", method="GET", **params)
    
    def get_poi_search(
        self,
        address: Optional[str] = None,
        zipcode: Optional[str] = None,
        point: Optional[str] = None,
        radius: Optional[float] = None,
        searchdistance: Optional[float] = None,
        categoryname: Optional[str] = None,
        categoryid: Optional[str] = None,
        lineofbusinessname: Optional[str] = None,
        industryname: Optional[str] = None,
        recordlimit: Optional[int] = None,
        page: int = 1,
        pagesize: int = 10
    ) -> Dict[str, Any]:
        """
        Search for points of interest in proximity to a location.
        
        Args:
            address: Street address to search near
            zipcode: ZIP code to search near
            point: Geographic point in format POINT(longitude,latitude)
            radius: Search radius in miles
            searchdistance: Search distance in miles
            categoryname: POI category name filter
            categoryid: POI category ID filter
            lineofbusinessname: Line of business filter
            industryname: Industry name filter
            recordlimit: Maximum number of results
            page: Page number (default: 1)
            pagesize: Results per page (default: 10)
            
        Returns:
            Response containing POI array
        """
        params = {
            k: v for k, v in {
                "address": address,
                "zipcode": zipcode,
                "point": point,
                "radius": radius,
                "searchdistance": searchdistance,
                "categoryname": categoryname,
                "categoryid": categoryid,
                "lineofbusinessname": lineofbusinessname,
                "industryname": industryname,
                "recordlimit": recordlimit,
                "page": page,
                "pagesize": pagesize
            }.items() if v is not None
        }
        
        return self._call("get_poi_search", method="GET", **params)


def main():
    """
    Practical workflow example: Find properties in an area, retrieve detailed
    information including owner data, and identify nearby points of interest.
    """
    # Initialize the client
    client = ParseClient()
    
    print("=" * 80)
    print("ATTOM Data API - Property Research & Neighborhood Analysis Workflow")
    print("=" * 80)
    
    # Step 1: Search for properties by address
    print("\n[Step 1] Searching for properties in Beverly Hills...")
    search_response = client.get_property_address(
        address1="Beverly Hills",
        address2="CA",
        pagesize=3
    )
    
    if search_response.get("status", {}).get("code") != 0:
        print(f"Error: {search_response.get('status', {}).get('msg')}")
        return
    
    properties = search_response.get("property", [])
    total_found = search_response.get("status", {}).get("total", 0)
    print(f"\nFound {total_found} properties. Analyzing first {len(properties)}:\n")
    
    property_details = []
    
    # Step 2: Get detailed information and owner data for each property
    print("[Step 2] Retrieving detailed property and owner information...\n")
    
    for idx, prop in enumerate(properties, 1):
        prop_id = prop.get("identifier", {}).get("attomId")
        address_oneline = prop.get("address", {}).get("oneLine", "N/A")
        
        print(f"Property {idx}: {address_oneline}")
        
        if not prop_id:
            print("  (No ATTOM ID available)\n")
            continue
        
        # Get owner information
        try:
            owner_response = client.get_property_detail_owner(attomid=str(prop_id))
            
            if owner_response.get("status", {}).get("code") == 0:
                owner_data = owner_response.get("property", [{}])[0]
                owner_info = owner_data.get("owner", {}).get("owner1", {})
                owner_name = owner_info.get("fullname", "N/A")
                
                print(f"  ATTOM ID: {prop_id}")
                print(f"  Current Owner: {owner_name}")
                
                # Store for later analysis
                property_details.append({
                    "attomid": prop_id,
                    "address": address_oneline,
                    "owner": owner_name
                })
            else:
                print(f"  ATTOM ID: {prop_id}")
                print("  Owner Info: Unable to retrieve")
        except Exception as e:
            print(f"  ATTOM ID: {prop_id}")
            print(f"  Owner Info: Error - {str(e)[:60]}")
        
        print()
    
    # Step 3: Find points of interest in the area
    print("[Step 3] Searching for points of interest near the properties...\n")
    
    try:
        poi_response = client.get_poi_search(
            address="Beverly Hills, CA",
            searchdistance=1,
            recordlimit=10
        )
        
        if poi_response.get("status", {}).get("code") == 0:
            pois = poi_response.get("poi", [])
            total_pois = poi_response.get("status", {}).get("total", 0)
            
            if pois:
                print(f"Found {total_pois} points of interest. Showing first {len(pois)}:\n")
                print(f"{'Business Name':<40} {'Category':<30} {'Distance':<10}")
                print("-" * 80)
                
                for poi in pois[:5]:
                    biz_name = poi.get("businessLocation", {}).get("businessStandardName", "N/A")
                    category = poi.get("category", {}).get("condensedHeading", "N/A")
                    distance = poi.get("details", {}).get("distance", "N/A")
                    
                    # Truncate long names for display
                    biz_name_display = (biz_name[:39] + "...") if len(biz_name) > 40 else biz_name
                    category_display = (category[:29] + "...") if len(category) > 30 else category
                    
                    print(f"{biz_name_display:<40} {category_display:<30} {distance:<10}")
                
                print()
            else:
                print("No points of interest found in this area.\n")
        else:
            print("Unable to retrieve POI data.\n")
    except Exception as e:
        print(f"POI search failed: {str(e)[:80]}\n")
    
    # Step 4: Summary
    print("=" * 80)
    print("Summary of Found Properties:")
    print("=" * 80)
    
    if property_details:
        print(f"\n{'Address':<50} {'Owner':<25}")
        print("-" * 75)
        for prop in property_details:
            address_display = (prop["address"][:49] + "...") if len(prop["address"]) > 50 else prop["address"]
            owner_display = (prop["owner"][:24] + "...") if len(prop["owner"]) > 25 else prop["owner"]
            print(f"{address_display:<50} {owner_display:<25}")
    else:
        print("\nNo property details retrieved.")
    
    print("\n" + "=" * 80)
    print("Workflow complete!")
    print("=" * 80)


if __name__ == "__main__":
    main()
All endpoints · 5 totalmissing one? ·

Returns properties within a specified area or matching address criteria.

Input
ParamTypeDescription
pageintegerPage number
radiusnumberSearch radius in miles
address1stringStreet address line 1
address2stringCity, state, zip
latitudenumberLatitude for radius search
pagesizeintegerResults per page
longitudenumberLongitude for radius search
postalcodestringPostal/ZIP code to search
propertytypestringProperty type filter
Response
{
  "type": "object",
  "fields": {
    "status": "object",
    "property": "array"
  },
  "sample": {
    "data": {
      "status": {
        "msg": "SuccessWithResult",
        "code": 0,
        "page": 1,
        "total": 9429,
        "version": "1.0.0",
        "pagesize": 2,
        "transactionID": "f4bf202a669467a2d0cdb7760cc83e59",
        "responseDateTime": "2026-02-26T00:22:43.312Z"
      },
      "property": [
        {
          "address": {
            "line1": "502 N ALTA DR",
            "line2": "BEVERLY HILLS, CA 90210",
            "country": "US",
            "oneLine": "502 N ALTA DR, BEVERLY HILLS, CA 90210",
            "postal1": "90210",
            "postal2": "3502",
            "postal3": "C011",
            "locality": "BEVERLY HILLS",
            "matchCode": "ExaStr",
            "countrySubd": "CA"
          },
          "vintage": {
            "pubDate": "2025-10-14",
            "lastModified": "2025-10-14"
          },
          "location": {
            "geoid": "CO06037, CS0691750, ND0004780424, PL0606308, SB0000068678, SB0000068680, ZI90210",
            "geoIdV4": {
              "CO": "c901b0c78f08f2e777cce3d16936fd0e",
              "CS": "c5d84093eb167032a75ea743fd5f2f81",
              "DB": "938838cffdba658277c328179d6d9166",
              "N1": "ba85655100a0573f28372da416303c28",
              "PL": "759869c1bf6d2bca73c7291f9a7959f0",
              "SB": "455e6b356ca9102619ffa1aa7dbbcae8, 3b1c5e204f0c6fd74e749b74c2449b0c",
              "ZI": "ecf7b806ad7dea1def2c60b246364d06"
            },
            "accuracy": "Rooftop",
            "distance": 0,
            "latitude": "34.079958",
            "longitude": "-118.392471"
          },
          "identifier": {
            "Id": 111904,
            "apn": "4341-003-001",
            "fips": "06037",
            "attomId": 111904
          }
        },
        {
          "address": {
            "line1": "511 N BEDFORD DR",
            "line2": "BEVERLY HILLS, CA 90210",
            "country": "US",
            "oneLine": "511 N BEDFORD DR, BEVERLY HILLS, CA 90210",
            "postal1": "90210",
            "postal2": "3213",
            "postal3": "C026",
            "locality": "BEVERLY HILLS",
            "matchCode": "ExaStr",
            "countrySubd": "CA"
          },
          "vintage": {
            "pubDate": "2025-10-14",
            "lastModified": "2025-10-14"
          },
          "location": {
            "geoid": "CO06037, CS0691750, DB0604830, ND0004780424, PL0606308, ZI90210",
            "geoIdV4": {
              "CO": "c901b0c78f08f2e777cce3d16936fd0e",
              "CS": "c5d84093eb167032a75ea743fd5f2f81",
              "DB": "938838cffdba658277c328179d6d9166",
              "N1": "ba85655100a0573f28372da416303c28",
              "PL": "759869c1bf6d2bca73c7291f9a7959f0",
              "SB": "518dd0b4740f4cc4327409bfa11c3ddb, 3b1c5e204f0c6fd74e749b74c2449b0c",
              "ZI": "ecf7b806ad7dea1def2c60b246364d06"
            },
            "accuracy": "Rooftop",
            "distance": 0,
            "latitude": "34.070421",
            "longitude": "-118.408060"
          },
          "identifier": {
            "Id": 156593,
            "apn": "4345-028-003",
            "fips": "06037",
            "attomId": 156593
          }
        }
      ]
    },
    "status": "success"
  }
}

About the ATTOM Data API

Property Search and Detail

get_property_address accepts street address components (address1, address2), a postal code, or a latitude/longitude pair with an optional radius (in miles) to return a paginated property array matching those criteria. get_property_detail narrows to a single property using either an attomid or a full address, returning a rich property array with detailed attribute fields. get_property_detail_owner requires an attomid and adds current owner information to that same structure — ownership data is only accessible through this dedicated endpoint, not through the general detail call.

Sales Trends by Geography

get_transaction_sales_trend returns a salesTrends array for a target geography defined by a geoidv4 — a 32-character hex identifier available in the geoIdV4 field of property responses. Required inputs are startyear, endyear, and geoidv4. The interval parameter accepts 'yearly' or 'monthly'. Geography type is encoded in the ID prefix: ZI for ZIP code, CO for county, PL for place, CS for census tract, and DB for district. An optional propertytype filter narrows results to a specific asset class.

Points of Interest

get_poi_search returns a poi array for locations near an address, ZIP code, or a WKT-style point string formatted as POINT(longitude,latitude). A categoryid parameter filters by POI type. The recordlimit parameter caps results (default 20), and standard page/pagesize parameters control pagination across larger result sets.

Reliability & maintenance

The ATTOM Data API is a managed, monitored endpoint for api.developer.attomdata.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when api.developer.attomdata.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.

This isn't an official api.developer.attomdata.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.

Will this API break when the source site changes?+
It's built not to. Every endpoint is health-checked on a schedule with automated test probes. When the source site changes and a check fails, the API is automatically queued for repair and re-verified — that's the self-healing layer. Each API page shows when its endpoints were last verified. And because marketplace APIs are shared, any fix reaches everyone using it.
Is this an official API from the source site?+
No — Parse APIs are independent, managed REST wrappers over publicly available data. That is the point: where a site has no official API (or only a limited one), Parse gives you a maintained, monitored endpoint for that data and keeps it working as the site changes — so you get a stable contract over a source that never promised one.
Can I fix or extend this API myself if I need a new endpoint or field?+
Yes — and you don't have to wait on us. This API was generated by the Parse agent, which stays attached. Describe the change in plain English ("add an endpoint that returns reviews", "fix the price field") in the revise box on the API page or via the revise_api MCP tool, and the agent rebuilds it against the live site in minutes. Contributing the change back to the public API is free.
What happens if I call an endpoint that has an issue?+
Errors are machine-readable: a bad call returns a clean status with the list of available endpoints and a repair hint, so an agent (or you) can recover or trigger a fix instead of failing silently. Confirmed failures feed the automatic repair queue.
Common use cases
  • Enriching property listings with ownership records pulled from get_property_detail_owner
  • Building neighborhood scorecards by querying nearby amenities via get_poi_search with categoryid filters
  • Generating market trend dashboards using get_transaction_sales_trend with monthly interval data over multi-year ranges
  • Geocoding-free property lookups by passing address1 and address2 to get_property_detail
  • Radius-based property prospecting by supplying latitude, longitude, and a mile radius to get_property_address
  • Comparing sales velocity across ZIP codes and counties by switching geoidv4 prefixes in trend queries
  • Populating CRM records with structured property attributes from the property response array
Pricing & limitsSee full pricing →
TierPriceCredits/monthRate limit
Free$0/mo1005 req/min
Hobby$30/mo1,00020 req/min
Developer$100/mo5,000100 req/min

One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.

Frequently asked questions
Does ATTOM Data have an official developer API?+
Yes. ATTOM Data Solutions provides an official API documented at https://api.developer.attomdata.com. The Parse API surfaces a curated subset of that platform's property, owner, POI, and sales trend endpoints.
How do I obtain the `geoidv4` value needed for sales trend queries?+
get_transaction_sales_trend requires a geoidv4 that comes from the geoIdV4 field returned in property responses — for example, from get_property_detail. The prefix of the 32-character hex hash encodes geography type: ZI for ZIP code, CO for county, PL for place, CS for census, and DB for district. There is no dedicated endpoint to look up geography IDs independently.
Does the API return historical ownership or mortgage/lien data?+
Not currently. Owner data is limited to current ownership information from get_property_detail_owner. Historical ownership chains, mortgage records, and lien data are not exposed by these endpoints. You can fork this API on Parse and revise it to add endpoints that cover those additional ATTOM data products.
What is the difference between `get_property_detail` and `get_property_detail_owner`?+
get_property_detail accepts either an attomid or an address pair and returns property attribute data. get_property_detail_owner requires an attomid and returns that same property structure augmented with current owner information. Owner fields are not present in the base detail response.
Are property valuation estimates (AVMs) or rental estimates included in any endpoint?+
Not currently. The five endpoints cover property attributes, owner records, POI proximity, and sales trend aggregates. Automated valuation model (AVM) outputs and rental price estimates are not part of this API's response fields. You can fork this API on Parse and revise it to add an endpoint targeting ATTOM's valuation data products.
Page content last updated . Spec covers 5 endpoints from api.developer.attomdata.com.
Related APIs in Real EstateSee all →
realtor.com API
Search millions of real estate listings on Realtor.com, view detailed property information, find qualified agents in your area, and access market analytics to understand pricing trends. Get location suggestions and property insights all in one place to help you make informed decisions about buying, selling, or investing in real estate.
auction.com API
auction.com API
domain.com.au API
Search and compare property listings for sale, rent, or sold properties across Australia, view detailed property information and agent profiles, and explore suburb insights to make informed real estate decisions. Access comprehensive data on agents, neighborhoods, and properties all in one place.
qPublic Property Records API
Search for properties on qPublic and access comprehensive details including owner information, valuations, building and land characteristics, sales history, tax exemptions, and fees. Retrieve complete property records to research real estate, verify ownership, or analyze property values.
trulia.com API
Search real estate listings for properties available for sale, rent, or recently sold, and access detailed information like property photos, price history, nearby schools, and local amenities. Compare similar homes, calculate mortgage estimates, and make informed decisions with comprehensive property data all in one place.
99acres.com API
99acres.com API
exprealty.com API
Search eXp Realty property listings by location with pagination, and access detailed information like images, agent contacts, and property history for any listing. Discover new locations and browse available homes all in one integrated experience.
realestate.com.au API
Search and retrieve property listing data from realestate.com.au, including listings for sale and rent, detailed property information, and nearby schools for any location across Australia.