This repository contains two scripts that scan HubSpot records and validate/normalize their State/Region and State/Region Code properties against a predefined list of US states, Canadian provinces, and Mexican states:
verify_company_states.py— validates Company records.normalize_contact_states.py— normalizes Contact records.
Both provide automated corrections for common spelling errors and abbreviations, and offer an interactive interface to manually correct or skip records that cannot be resolved automatically. Records identified as being in countries outside the target regions (US, Canada, and Mexico) are automatically skipped.
- Paging Support: Fetches and processes all records in batches of 100 via HubSpot CRM v3 API.
- Foreign Country Filtering: Automatically ignores records located outside of the target regions (US, Canada, and Mexico) if their country field is populated. This prevents false positive suggestion prompts for foreign state/region names (e.g., "Madrid" in Spain or "Bavaria" in Germany).
- Fuzzy Matching: Automatically suggests spelling corrections for state/region names (e.g., misspelled names or variations).
- Auto-expansion: Expands 2-letter abbreviations to their full names (e.g.,
CA->California). - Country Disambiguation: Uses the record's country (US, Canada, Mexico) to resolve overlapping 2-letter codes (e.g.,
BCfor British Columbia, Canada vs. Baja California, Mexico). - Auto-population: Automatically populates the 2-letter State/Region Code field if only the full name is present, or vice-versa when safe.
- Interactive Correction Loop: Allows users to approve suggestions, enter manual entries, skip records, or quit.
- Checkpointing: Automatically saves progress to a checkpoint file (
company_states_checkpoint.jsonfor companies,contact_states_checkpoint.jsonfor contacts) so you can stop and resume either script at any time without losing progress. - Detailed Logging: Records actions to stdout and to a log file (
verify_company_states.logfor companies,normalize_contact_states.logfor contacts).
- Python 3.6+
- HubSpot Private App Token: Requires read/write access to CRM Company objects (for the company script) and/or CRM Contact objects (for the contact script).
- HubSpot Portal ID: Your HubSpot Account/Portal ID (e.g.,
12345678).
-
Clone or Navigate to the Repository Directory:
cd /path/to/state-validator -
Create and Activate a Virtual Environment (Recommended): On macOS/Linux:
python3 -m venv .venv source .venv/bin/activateOn Windows:
python -m venv .venv .venv\Scripts\activate
-
Install Dependencies:
pip install -r requirements.txt
Execute the script by providing your HubSpot Portal ID as a positional argument.
python verify_company_states.py <portal_id> [--state-code-prop <property_name>]portal_id: (Required) Your HubSpot account's Portal ID (e.g.,12345678).--state-code-prop: (Optional) The internal API name of the State/Region Code property in HubSpot. Defaults tostate_code. The script also automatically scans and checks forhs_state_codeandstate_region_code.
python verify_company_states.py 12345678-
Token Input: You will be prompted to enter your HubSpot Private App Token securely:
HubSpot Private App Token :(Note: The characters will not display as you type for security.)
-
Property Detection: The script will verify if the specified State/Region Code property exists. If the property is not found, you will be warned and asked if you want to proceed.
-
Pre-scan: The script fetches all companies, bypasses empty records, and automatically corrects standard abbreviation or missing code cases.
-
Interactive Validation Loop: For remaining invalid state/region values, the script prints details and prompts you for action:
Company [1/15]: Acme Corp (acme.com) | ID: 123456789 Current Country : US Current State/Region: 'Californa' Current Code : 'CA' ------------------------------------------------------------ [Suggestion] Full Name: California [Suggestion] Code : CA Choose action ([A]ccept Suggestion | [M]anual Entry | [S]kip | [Q]uit) [A]:[A]ccept Suggestion: Applies the suggested full name and/or code to the company in HubSpot.[M]anual Entry: Prompts you to type a state name (must match allowed states or be explicitly overridden) and code.[S]kip: Skips the company for this run.[Q]uit: Safely saves progress to the checkpoint file and terminates execution.
The contact normalizer works the same way, but operates on Contact records. Execute it by providing your HubSpot Portal ID as a positional argument.
python normalize_contact_states.py <portal_id> [--state-code-prop <property_name>] [--test]portal_id: (Required) Your HubSpot account's Portal ID (e.g.,12345678).--state-code-prop: (Optional) The internal API name of the State/Region Code property on the Contact object. Defaults tostate_code. The script also automatically scans and checks forhs_state_codeandstate_region_code.--test: (Optional) Test mode. Fetches and processes only the first 100 contacts, so you can verify the script's behavior on a small sample before a full run. Updates are still written to HubSpot for those 100 contacts. The checkpoint file is retained after a test run, so a later full run resumes past the records already processed.
python normalize_contact_states.py 12345678The flow is identical to the company script: you are prompted for your Private App Token, the State/Region Code property is detected/verified, all contacts are fetched and pre-scanned (auto-correcting abbreviations, casing, and missing codes), and any remaining unresolved records enter the interactive validation loop. Contacts are displayed by name (or email if no name is set):
Contact [1/15]: Jane Doe (jane.doe@example.com) | ID: 123456789
Current Country : US
Current State/Region: 'Californa'
Current Code : 'CA'
------------------------------------------------------------
[Suggestion] Full Name: California
[Suggestion] Code : CA
Choose action ([A]ccept Suggestion | [M]anual Entry | [S]kip | [Q]uit) [A]:
Note: Portals typically contain far more contacts than companies, so the initial fetch and pre-scan may take a while. The checkpoint file means you can safely quit and resume at any time.
If you quit either script midway (using q or Ctrl+C), a checkpoint file will be saved — company_states_checkpoint.json for the company script, contact_states_checkpoint.json for the contact script. The next time you run that script, it will automatically detect the checkpoint and resume from where you left off. The checkpoint file is automatically deleted once all invalid records are successfully processed. The two scripts use separate checkpoint files, so they can be run independently without interfering with each other.
All changes and exceptions are logged with timestamps in verify_company_states.log (companies) and normalize_contact_states.log (contacts). You can tail or inspect these files to verify what updates were applied to your HubSpot portal:
tail -f verify_company_states.log
tail -f normalize_contact_states.log