Open To Close API Python Client

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!
✨ Features
- 🏠 Complete Property Management - Full CRUD operations for properties, listings, and transactions
- 👥 Contact & Team Management - Manage agents, contacts, teams, and user relationships
- 📄 Document Tracking - Handle property documents, emails, notes, and tasks
- 🔒 Type Safety - Full type hints and IDE support for better development experience
- 🛡️ Robust Error Handling - Comprehensive exception handling with detailed error messages
- ⚡ Production Ready - Built-in rate limiting, retry logic, and authentication management
- 🧪 Well Tested - Comprehensive test suite with 100% coverage
- 📚 Excellent Documentation - Complete guides, examples, and API reference
- ✅ Endpoint Reliability - All 6 core API endpoints tested and verified working (100% success rate)
- 🔧 Smart URL Routing - Automatic handling of different URL patterns for optimal API compatibility
- 🎯 Simplified Property Creation - Create properties with just a title or simple dictionary format
- 🆕 Dynamic Field Mapping - Automatic field ID translation with human-readable names (v2.5.0)
- 🔍 Field Discovery - List and validate available fields before API calls (v2.5.0)
- 🎛️ Smart Defaults - Automatic team member detection and sensible field defaults (v2.5.0)
🚀 Quick Start
Installation
pip install open-to-close
Authentication
Set your API key as an environment variable:
export OPEN_TO_CLOSE_API_KEY="your_api_key_here"
Basic Usage
from open_to_close import OpenToCloseAPI
client = OpenToCloseAPI()
property_data = client.properties.create_property({
"title": "Beautiful Family Home",
"client_type": "Buyer",
"status": "Active",
"purchase_amount": 450000
})
simple_property = client.properties.create_property("Downtown Condo")
properties = client.properties.list_properties()
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']}")
🎯 Simplified Property Creation
Creating properties is now incredibly easy! Choose from multiple input formats:
property1 = client.properties.create_property("Beautiful Family Home")
property2 = client.properties.create_property({
"title": "Luxury Estate with Pool",
"client_type": "Buyer",
"status": "Active",
"purchase_amount": 650000
})
property3 = client.properties.create_property({
"title": "Downtown Condo for Sale",
"client_type": "Seller",
"status": "Pre-MLS",
"purchase_amount": 425000
})
What happens automatically:
- 🔍 Auto-detects team member ID from your teams
- 🎯 Smart defaults: Client type = "Buyer", Status = "Active"
- 🛡️ Input validation with clear error messages
- 🔄 Format conversion to complex API structure behind the scenes
- ⚡ Legacy support - advanced format still works for power users
👉 See Property Creation Guide for complete examples and options
🔍 Field Discovery & Validation (v2.5.0)
Discover available fields and validate data before making API calls:
fields = client.list_available_fields()
print(f"Found {len(fields)} available fields")
for field in fields[:5]:
required = "✅ Required" if field['required'] else "⭕ Optional"
print(f"{field['key']}: {field['title']} ({field['type']}) - {required}")
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:
- 🔍 Dynamic Field Lookup - Fetch current field definitions from API
- 🏷️ Field Metadata - Get field types, requirements, and available options
- ✅ Pre-Validation - Validate data before API calls to prevent errors
- 🔄 Auto-Refresh - Field mappings update automatically when API changes
🏠 Complete Transaction Workflow
Create complete real estate transactions with properties and associated contacts:
from open_to_close import OpenToCloseAPI
client = OpenToCloseAPI()
property_result = client.properties.create_property({
"title": "123 Main Street Sale Transaction",
"client_type": "Buyer",
"status": "Active",
"purchase_amount": 450000
})
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"
}
]
created_contacts = []
for contact_data in contacts:
contact = client.contacts.create_contact(contact_data)
created_contacts.append(contact)
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
🛡️ Reliability & Testing
This API wrapper has undergone comprehensive testing and debugging to ensure production reliability:
- ✅ All 6 Core Endpoints Verified - Properties, Contacts, Agents, Teams, Users, and Tags
- ✅ 100% CRUD Success Rate - Create, Read, Update, Delete operations all working
- ✅ URL Pattern Resolution - Smart routing handles API's different URL patterns automatically
- ✅ Production Testing - Extensively tested with real API calls and edge cases
For detailed information about the testing and debugging process, see:
📋 Core Resources
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() |
🏗️ Property Sub-Resources
Extend property functionality with related data:
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) |
🎯 Real-World Example
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()
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"
})
seller = client.contacts.create_contact({
"first_name": "Sarah",
"last_name": "Johnson",
"email": "sarah.johnson@email.com"
})
client.property_contacts.create_property_contact(
property_data["id"],
{
"contact_id": seller["id"],
"role": "Seller",
"primary": True
}
)
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"
}
)
client.property_notes.create_property_note(
property_data["id"],
{
"content": "Property onboarding completed. Ready for marketing.",
"note_type": "Listing"
}
)
return property_data
new_property = onboard_new_listing()
print(f"Successfully onboarded property: {new_property['address']}")
🔧 Advanced Configuration
Environment-Specific Setup
from open_to_close import OpenToCloseAPI
prod_client = OpenToCloseAPI(
api_key="prod_key_here",
base_url="https://api.opentoclose.com/v1"
)
dev_client = OpenToCloseAPI(
api_key="dev_key_here",
base_url="https://dev-api.opentoclose.com/v1"
)
Error Handling
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...")
📚 Documentation
🧪 Testing
Run the test suite:
pip install -e ".[dev]"
pytest --cov=open_to_close --cov-report=term-missing
python -m pytest tests/
🛠️ Development
Setup Development Environment
git clone https://github.com/theperrygroup/open-to-close.git
cd open-to-close
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pre-commit install
Code Quality
This project maintains high code quality standards:
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
Quick Contribution Steps
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
)
- Make your changes with tests
- Ensure all tests pass (
pytest
)
- Commit your changes (
git commit -am 'Add amazing feature'
)
- Push to the branch (
git push origin feature/amazing-feature
)
- Open a Pull Request
📋 Requirements
- Python: 3.8 or higher
- Dependencies:
requests>=2.25.0
, python-dotenv>=0.19.0
- API Access: Valid Open To Close API key
🔗 Links
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🏢 About The Perry Group
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