A comprehensive Python wrapper for the Dotloop API v2, providing easy access to real estate transaction management and document handling functionality.
- Complete API Coverage: Implements all major Dotloop API endpoints
- Type Safety: Full type hints and Pydantic validation
- Error Handling: Comprehensive exception handling with detailed error messages
- Easy to Use: Intuitive client interface with method chaining
- Well Documented: Extensive docstrings and examples
- Testing: 100% test coverage with pytest
pip install dotloop-apifrom dotloop import DotloopClient, TransactionType, LoopStatus
# Initialize the client
client = DotloopClient(api_key="your_api_key")
# Or use environment variable DOTLOOP_API_KEY
client = DotloopClient()
# Get account information
account = client.account.get_account()
print(f"Account: {account['data']['firstName']} {account['data']['lastName']}")
# List profiles
profiles = client.profile.list_profiles()
for profile in profiles['data']:
print(f"Profile: {profile['name']}")
# Create a new loop
loop = client.loop_it.create_loop(
name="123 Main St Property",
transaction_type=TransactionType.PURCHASE_OFFER,
status=LoopStatus.PRE_OFFER,
profile_id=123,
street_number="123",
street_name="Main St",
city="San Francisco",
state="CA",
zip_code="94105"
)The Dotloop API uses OAuth 2.0 Bearer tokens for authentication. You can obtain an access token through the OAuth flow and then use it with this wrapper.
-
Environment Variable (Recommended):
export DOTLOOP_API_KEY="your_access_token"
-
Direct Parameter:
client = DotloopClient(api_key="your_access_token")
client.account.get_account()- Get current account details
client.profile.list_profiles()- List all profilesclient.profile.get_profile(profile_id)- Get profile by IDclient.profile.create_profile(...)- Create a new profileclient.profile.update_profile(profile_id, ...)- Update profile
client.loop.list_loops(profile_id, ...)- List loops for a profileclient.loop.get_loop(profile_id, loop_id)- Get loop by IDclient.loop.create_loop(profile_id, ...)- Create a new loopclient.loop.update_loop(profile_id, loop_id, ...)- Update loop
client.loop_it.create_loop(...)- Create loop with property and participant data
client.contact.list_contacts()- List all contactsclient.contact.get_contact(contact_id)- Get contact by IDclient.contact.create_contact(...)- Create a new contactclient.contact.update_contact(contact_id, ...)- Update contactclient.contact.delete_contact(contact_id)- Delete contact
Dotloop webhook subscriptions are created against a (targetType, targetId) pair, and event types are target-specific. In practice, “subscribe to everything valid” usually means two subscriptions:
PROFILEtarget (account.defaultProfileId): loop + participant eventsUSERtarget (account.id): contact + profile membership events
Use ensure_default_subscriptions() to set this up idempotently:
from dotloop import DotloopClient
client = DotloopClient()
results = client.webhook.ensure_default_subscriptions(
"https://example.com/dotloop-webhook",
enabled=True,
signing_key="super_secret_key", # optional
external_id="my_external_id", # optional
)
for result in results:
print(result.action, result.target_type, result.target_id, result.subscription_id)Note: Dotloop rejects invalid eventTypes + targetType combinations. Use SUPPORTED_WEBHOOK_EVENT_TYPES_BY_TARGET (and/or the ensure helpers above) to avoid request validation errors.
from dotloop import DotloopClient, TransactionType, LoopStatus, ParticipantRole
client = DotloopClient()
# Create a comprehensive loop with property and participants
loop = client.loop_it.create_loop(
name="John Doe - 456 Oak Avenue",
transaction_type=TransactionType.PURCHASE_OFFER,
status=LoopStatus.PRE_OFFER,
profile_id=123,
# Property information
street_number="456",
street_name="Oak Avenue",
city="Los Angeles",
state="CA",
zip_code="90210",
# Participants
participants=[
{
"fullName": "John Doe",
"email": "john.doe@example.com",
"role": ParticipantRole.BUYER.value
},
{
"fullName": "Jane Smith",
"email": "jane.smith@realty.com",
"role": ParticipantRole.BUYING_AGENT.value
},
{
"fullName": "Bob Johnson",
"email": "bob.johnson@realty.com",
"role": ParticipantRole.LISTING_AGENT.value
}
],
# Optional MLS information
mls_property_id="ML123456",
template_id=1001
)
print(f"Created loop: {loop['data']['loopUrl']}")# Create a new contact
contact = client.contact.create_contact(
first_name="Alice",
last_name="Johnson",
email="alice.johnson@example.com",
phone="+1 (555) 123-4567",
company="Johnson Realty"
)
# Update the contact
updated_contact = client.contact.update_contact(
contact_id=contact['data']['id'],
phone="+1 (555) 987-6543",
company="Johnson Premium Realty"
)
# List all contacts
contacts = client.contact.list_contacts()
for contact in contacts['data']:
print(f"{contact['firstName']} {contact['lastName']} - {contact.get('email', 'No email')}")# Create a new profile
profile = client.profile.create_profile(
name="My Real Estate Business",
company="ABC Realty",
phone="+1 (555) 123-4567",
address="123 Business Ave",
city="New York",
state="NY",
zip_code="10001"
)
# List loops for this profile
loops = client.loop.list_loops(
profile_id=profile['data']['id'],
batch_size=50,
sort="updated:desc",
include_details=True
)The wrapper provides comprehensive error handling with specific exception types:
from dotloop import DotloopClient
from dotloop.exceptions import (
AuthenticationError,
NotFoundError,
ValidationError,
RateLimitError
)
client = DotloopClient()
try:
account = client.account.get_account()
except AuthenticationError:
print("Invalid or expired API token")
except NotFoundError:
print("Resource not found")
except ValidationError as e:
print(f"Invalid parameters: {e}")
except RateLimitError:
print("Rate limit exceeded, please wait")The wrapper provides enums for common values:
from dotloop import (
TransactionType,
LoopStatus,
ParticipantRole,
SortDirection
)
# Transaction types
TransactionType.PURCHASE_OFFER
TransactionType.LISTING_FOR_SALE
TransactionType.PURCHASED
TransactionType.SOLD
# Loop statuses
LoopStatus.PRE_OFFER
LoopStatus.UNDER_CONTRACT
LoopStatus.SOLD
LoopStatus.ARCHIVED
# Participant roles
ParticipantRole.BUYER
ParticipantRole.SELLER
ParticipantRole.LISTING_AGENT
ParticipantRole.BUYING_AGENT# Clone the repository
git clone https://github.com/your-org/dotloop-python.git
cd dotloop-python
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements-dev.txt
# Run tests
pytest
# Run tests with coverage
pytest --cov=dotloop --cov-report=html# Run all tests
pytest
# Run with coverage
pytest --cov=dotloop
# Run specific test file
pytest tests/test_account.py
# Run with verbose output
pytest -vFor detailed API documentation, see the official Dotloop API documentation.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Documentation: API Documentation
- Issues: GitHub Issues
- Email: dev@theperry.group
- Initial release
- Complete implementation of core Dotloop API endpoints
- Account, Profile, Loop, Loop-It, and Contact management
- Comprehensive error handling and type safety
- 100% test coverage