
Product
Introducing Socket MCP for Claude Desktop
Add secure dependency scanning to Claude Desktop with Socket MCP, a one-click extension that keeps your coding conversations safe from malicious packages.
A modern, type-safe Python wrapper for the Open To Close API. Manage properties, agents, contacts, and more with a clean, intuitive interface designed for real estate professionals and developers.
🎉 Latest Update: All API endpoint issues have been fully resolved! The wrapper now features 100% working CRUD operations across all endpoints with production-ready reliability.
🚀 v2.5.0 NEW: Dynamic Field Mapping - Revolutionary simplified property creation with automatic field ID translation and human-readable field names!
pip install open-to-close
Set your API key as an environment variable:
export OPEN_TO_CLOSE_API_KEY="your_api_key_here"
from open_to_close import OpenToCloseAPI
# Initialize the client
client = OpenToCloseAPI()
# Create a new property (simplified format) 🆕
property_data = client.properties.create_property({
"title": "Beautiful Family Home",
"client_type": "Buyer",
"status": "Active",
"purchase_amount": 450000
})
# Or even simpler - just pass a title!
simple_property = client.properties.create_property("Downtown Condo")
# Get all properties
properties = client.properties.list_properties()
# Add a note to the property
note = client.property_notes.create_property_note(
property_data["id"],
{"content": "Initial property intake completed"}
)
print(f"Created property {property_data['id']} with note {note['id']}")
Creating properties is now incredibly easy! Choose from multiple input formats:
# 1. Simple title only (uses smart defaults)
property1 = client.properties.create_property("Beautiful Family Home")
# 2. Dictionary with common fields
property2 = client.properties.create_property({
"title": "Luxury Estate with Pool",
"client_type": "Buyer", # "Buyer", "Seller", or "Dual"
"status": "Active", # "Active", "Pre-MLS", "Under Contract", etc.
"purchase_amount": 650000
})
# 3. Seller property
property3 = client.properties.create_property({
"title": "Downtown Condo for Sale",
"client_type": "Seller",
"status": "Pre-MLS",
"purchase_amount": 425000
})
What happens automatically:
👉 See Property Creation Guide for complete examples and options
Discover available fields and validate data before making API calls:
# List all available property fields with metadata
fields = client.list_available_fields()
print(f"Found {len(fields)} available fields")
# Show field details
for field in fields[:5]:
required = "✅ Required" if field['required'] else "⭕ Optional"
print(f"{field['key']}: {field['title']} ({field['type']}) - {required}")
# Validate property data before creation
property_data = {
"title": "Beautiful Home",
"client_type": "Buyer",
"status": "Active"
}
is_valid, errors = client.validate_property_data(property_data)
if is_valid:
property = client.properties.create_property(property_data)
print(f"✅ Created property {property['id']}")
else:
print(f"❌ Validation errors: {errors}")
Field Discovery Features:
Create complete real estate transactions with properties and associated contacts:
from open_to_close import OpenToCloseAPI
client = OpenToCloseAPI()
# Step 1: Create property (transaction)
property_result = client.properties.create_property({
"title": "123 Main Street Sale Transaction",
"client_type": "Buyer",
"status": "Active",
"purchase_amount": 450000
})
# Step 2: Create contacts with correct field format
contacts = [
{
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"phone": "+1-555-0101"
},
{
"first_name": "Sarah",
"last_name": "Johnson",
"email": "sarah.johnson@email.com",
"phone": "+1-555-0102"
}
]
# Create all contacts
created_contacts = []
for contact_data in contacts:
contact = client.contacts.create_contact(contact_data)
created_contacts.append(contact)
# Step 3: Link contacts to property
for contact in created_contacts:
association = client.property_contacts.create_property_contact(
property_id=property_result['id'],
contact_data={"contact_id": contact['id']}
)
print(f"Linked contact {contact['first_name']} {contact['last_name']}")
!!! warning "Important Contact Field Requirements"
🚨 Critical: The name
field is NOT supported by the Open To Close API. You must use first_name
and last_name
fields separately, or the API will return "Bad request" errors.
👉 See Property-Contact Workflow Guide for the complete tested workflow
This API wrapper has undergone comprehensive testing and debugging to ensure production reliability:
For detailed information about the testing and debugging process, see:
ENDPOINT_ISSUES_RESOLVED.md
- Complete resolution summarytests/INVESTIGATION_TESTS_README.md
- Investigation test documentationResource | Description | Example Usage |
---|---|---|
Properties | Manage real estate listings and transactions | client.properties.list_properties() |
Agents | Handle agent profiles and assignments | client.agents.list_agents() |
Contacts | Customer and lead management | client.contacts.create_contact(data) |
Teams | Team organization and structure | client.teams.list_teams() |
Users | User account management | client.users.retrieve_user(123) |
Tags | Classification and labeling | client.tags.list_tags() |
Extend property functionality with related data:
Sub-Resource | Description | Example Usage |
---|---|---|
Documents | File attachments per property | client.property_documents.list_property_documents(prop_id) |
Emails | Communication tracking | client.property_emails.create_property_email(prop_id, data) |
Notes | Internal annotations | client.property_notes.list_property_notes(prop_id) |
Tasks | Workflow management | client.property_tasks.create_property_task(prop_id, data) |
Contacts | Property-specific relationships | client.property_contacts.list_property_contacts(prop_id) |
Here's a complete workflow for onboarding a new property listing:
from open_to_close import OpenToCloseAPI
from datetime import datetime, timedelta
def onboard_new_listing():
client = OpenToCloseAPI()
# Create the property
property_data = client.properties.create_property({
"address": "456 Oak Avenue",
"city": "Los Angeles",
"state": "CA",
"zip_code": "90210",
"property_type": "Condo",
"bedrooms": 2,
"bathrooms": 2,
"listing_price": 750000,
"status": "Coming Soon"
})
# Create seller contact (using only reliably supported fields)
seller = client.contacts.create_contact({
"first_name": "Sarah",
"last_name": "Johnson",
"email": "sarah.johnson@email.com"
# Note: "contact_type" field has limited API support
})
# Link seller to property
client.property_contacts.create_property_contact(
property_data["id"],
{
"contact_id": seller["id"],
"role": "Seller",
"primary": True
}
)
# Add initial tasks
client.property_tasks.create_property_task(
property_data["id"],
{
"title": "Professional photography",
"due_date": (datetime.now() + timedelta(days=3)).strftime("%Y-%m-%d"),
"priority": "High"
}
)
# Log initial note
client.property_notes.create_property_note(
property_data["id"],
{
"content": "Property onboarding completed. Ready for marketing.",
"note_type": "Listing"
}
)
return property_data
# Run the workflow
new_property = onboard_new_listing()
print(f"Successfully onboarded property: {new_property['address']}")
from open_to_close import OpenToCloseAPI
# Production
prod_client = OpenToCloseAPI(
api_key="prod_key_here",
base_url="https://api.opentoclose.com/v1"
)
# Development
dev_client = OpenToCloseAPI(
api_key="dev_key_here",
base_url="https://dev-api.opentoclose.com/v1"
)
from open_to_close import OpenToCloseAPI
from open_to_close.exceptions import (
NotFoundError,
ValidationError,
AuthenticationError,
RateLimitError
)
client = OpenToCloseAPI()
try:
property_data = client.properties.retrieve_property(123)
except NotFoundError:
print("Property not found")
except ValidationError as e:
print(f"Invalid request: {e}")
except AuthenticationError:
print("Check your API key")
except RateLimitError:
print("Rate limit exceeded, retrying...")
Run the test suite:
# Install development dependencies
pip install -e ".[dev]"
# Run tests with coverage
pytest --cov=open_to_close --cov-report=term-missing
# Run with specific Python version
python -m pytest tests/
# Clone the repository
git clone https://github.com/theperrygroup/open-to-close.git
cd open-to-close
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install in development mode
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
This project maintains high code quality standards:
We welcome contributions! Please see our Contributing Guide for details.
git checkout -b feature/amazing-feature
)pytest
)git commit -am 'Add amazing feature'
)git push origin feature/amazing-feature
)requests>=2.25.0
, python-dotenv>=0.19.0
This project is licensed under the MIT License - see the LICENSE file for details.
The Open To Close API Python client is developed and maintained by The Perry Group, a leading real estate technology company.
Built with ❤️ for the real estate community
FAQs
Python wrapper for the Open To Close API
We found that open-to-close demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Add secure dependency scanning to Claude Desktop with Socket MCP, a one-click extension that keeps your coding conversations safe from malicious packages.
Product
Socket now supports Scala and Kotlin, bringing AI-powered threat detection to JVM projects with easy manifest generation and fast, accurate scans.
Application Security
/Security News
Socket CEO Feross Aboukhadijeh and a16z partner Joel de la Garza discuss vibe coding, AI-driven software development, and how the rise of LLMs, despite their risks, still points toward a more secure and innovative future.