A comprehensive Python client library for the Follow Up Boss API, providing easy access to all Follow Up Boss endpoints with full type safety and thorough documentation.
- Complete API Coverage: Support for all Follow Up Boss API endpoints
- Type Safety: Full type hints throughout the library
- Easy to Use: Simple, intuitive interface
- Well Documented: Comprehensive docstrings and examples
- Async Support: Built with modern Python async/await patterns
- Error Handling: Robust error handling and validation
- Extensible: Easy to extend for custom use cases
pip install follow-up-bossfrom follow_up_boss import FollowUpBossApiClient
# Initialize the client
client = FollowUpBossApiClient(
api_key="your_api_key",
x_system="Your-System-Name",
x_system_key="your_system_key"
)
# Get all people
people = client.people.get_all()
# Create a new person
new_person = client.people.create({
"name": "John Doe",
"email": "john@example.com",
"phone": "555-123-4567"
})
# Update a person
updated_person = client.people.update(person_id, {
"name": "John Smith"
})
# Add tags to a person (merge with existing, de-duplicated)
from follow_up_boss.people import People
people_api = People(client)
people_api.add_tags(person_id, ["Zillow: Note Logged"]) # merge=True by default
# Replace tags entirely
people_api.add_tags(person_id, ["Only These"], merge=False)
# Delete a person
client.people.delete(person_id)You can also configure the client using environment variables:
FOLLOW_UP_BOSS_API_KEY=your_api_key
X_SYSTEM=Your-System-Name
X_SYSTEM_KEY=your_system_keyfrom follow_up_boss import FollowUpBossApiClient
# Client will automatically use environment variables
client = FollowUpBossApiClient()The client provides access to all Follow Up Boss API resources:
- People: Manage contacts and leads
- Deals: Track real estate transactions (Commission Field Guide)
- Events: Handle activities and interactions
- Tasks: Manage todo items and follow-ups
- Notes: Add and retrieve notes
- Appointments: Schedule and manage appointments
- Text Messages: Send and receive SMS
- Email Templates: Manage email templates
- Text Message Templates: Manage SMS templates
- Webhooks: Configure webhook endpoints
- Reactions: Handle message reactions
- Teams: Manage team structures
- Users: Handle user accounts
- Groups: Organize contacts into groups
- Pipelines: Manage sales pipelines
- Stages: Configure pipeline stages
- Smart Lists: Dynamic contact lists
- Custom Fields: Define custom data fields
- Action Plans: Automated workflow templates
- Appointment Types: Configure appointment categories
- Appointment Outcomes: Track appointment results
- Person Attachments: File attachments for contacts
- Deal Attachments: File attachments for deals
The Deals API includes special handling for commission fields. Important: Commission fields must be passed as top-level parameters, not in custom_fields.
from follow_up_boss import Deals, DealsValidationError
deals_api = Deals(client)
# ✅ Correct - Commission fields as top-level parameters
deal = deals_api.create_deal(
name="123 Main Street",
stage_id=26,
price=450000,
commissionValue=13500.0,
agentCommission=9450.0,
teamCommission=4050.0
)
# ❌ Incorrect - This will raise DealsValidationError
try:
deal = deals_api.create_deal(
name="Deal Name",
stage_id=26,
custom_fields={'commissionValue': 13500} # This fails
)
except DealsValidationError as e:
print(f"Validation error: {e}")# Set commission using helper method
commission_data = {
'total': 15000.0,
'agent': 10500.0,
'team': 4500.0
}
updated_deal = deals_api.set_deal_commission(deal_id, commission_data)For complete commission field documentation, see the Commission Field Guide.
from follow_up_boss import FollowUpBossApiClient
from follow_up_boss.exceptions import ApiError, AuthenticationError
try:
client = FollowUpBossApiClient(api_key="invalid_key")
people = client.people.get_all()
except AuthenticationError:
print("Invalid API credentials")
except ApiError as e:
print(f"API Error: {e}")# Get all people with pagination
all_people = []
page = 1
while True:
response = client.people.get_all(page=page, limit=100)
people = response.get('people', [])
if not people:
break
all_people.extend(people)
page += 1from follow_up_boss import FollowUpBossApiClient
from follow_up_boss.people import People
client = FollowUpBossApiClient(
api_key="your_api_key",
x_system="Your-System-Name",
x_system_key="your_system_key",
)
people_api = People(client)
# Single page filtered by Smart List ID
page = people_api.list_people_by_list_id(154, limit=50)
# Fetch all pages (follows _metadata.next)
all_people = people_api.fetch_all_people_by_list_id(154, limit=100)Note: Cursor pagination uses the _metadata.next token returned by the API.
The People.iter_people() helper yields people across all pages. It prefers cursor-based pagination via _metadata.next when available (e.g., when using listId) and automatically falls back to offset-based pagination.
from follow_up_boss.people import People
people_api = People(client)
# Iterate with a saved list filter (cursor-based)
for person in people_api.iter_people({"listId": 154, "limit": 200}):
print(person.get("id"))
# Iterate with offset-based pagination (no cursor)
for person in people_api.iter_people({"limit": 100, "offset": 0, "stage": "New"}):
process(person)The client supports absolute-URL traversal via _metadata.nextLink to avoid deep pagination errors:
first = people_api.list_people({"listId": 154, "limit": 200})
meta = first.get("_metadata", {})
next_link = meta.get("nextLink")
while next_link:
page = people_api.list_people_next(next_link)
for person in page.get("people", []):
process(person)
next_link = page.get("_metadata", {}).get("nextLink")People.list_people(params) always returns a dictionary that includes at least:
{"people": [...], "count": int}This provides a stable structure for downstream clients even when the API omits certain fields.
The client exposes rate limit headers parsed from responses:
from follow_up_boss import FollowUpBossApiClient
client = FollowUpBossApiClient(api_key="...", x_system="...", x_system_key="...")
resp = client._get("people", params={"limit": 1})
print(resp.get("_rateLimit")) # {'limit': 120, 'remaining': 119, 'reset': 1710000000}
# Or access the last seen info directly
print(client.get_last_rate_limit())Explicit exceptions simplify handling and retries:
from follow_up_boss import (
FollowUpBossApiClient,
FollowUpBossAuthError,
FollowUpBossRateLimitError,
FollowUpBossValidationError,
)
client = FollowUpBossApiClient(api_key="...")
try:
client._get("people", {"limit": 1})
except FollowUpBossRateLimitError as e:
# backoff/retry using e.status_code and rate limit info
pass
except FollowUpBossAuthError as e:
# refresh credentials
pass
except FollowUpBossValidationError as e:
# fix request
pass# Add custom headers to requests
client = FollowUpBossApiClient(
api_key="your_key",
custom_headers={
"X-Custom-Header": "custom_value"
}
)git clone https://github.com/theperrygroup/follow-up-boss.git
cd follow-up-boss
pip install -e ".[dev]"pytestblack follow_up_boss tests
isort follow_up_boss testsmypy follow_up_boss- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for your changes
- Ensure all tests pass (
pytest) - Format your code (
blackandisort) - 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.
For questions, issues, or feature requests, please:
- Check the documentation
- Search existing issues
- Create a new issue if needed
- Major Commission Field Improvements: Added comprehensive commission field handling with validation
- Enhanced Error Messages: Context-specific error guidance for common mistakes
- New Validation System:
DealsValidationErrorfor deals-specific validation - Commission Helper Methods:
set_deal_commission()for easier commission management - Field Name Normalization: Consistent field naming between requests and responses
- Comprehensive Documentation: New commission field guide with examples and troubleshooting
- Enhanced Testing: Complete test coverage for all commission field scenarios
- Improved Developer Experience: Better error messages, helper properties, and validation
- People.add_tags helper: Official method to append/replace tags using PUT /people/{id}
- Merge behavior with de-duplication and optional case-insensitive matching
- Avoids unsupported POST /people/{id}/tags endpoint (404)
- Docs: README examples updated for tag operations
- Tooling: Formatting and type checks updated
- People: Added
list_people_by_list_idandfetch_all_people_by_list_idsupporting cursor pagination via_metadata.next.
- People: Added
iter_people(params)iterator (cursor-first, offset fallback). - People:
list_people(params)now guarantees a consistent shape:{"people": [...], "count": int}. - Typing: Introduced
ListPeopleParams,Person, andPeopleListResponseTypedDicts.
- Client: Added explicit exception subclasses for auth, rate limit, validation, not-found, and server errors.
- Client: Expose
_rateLimitmetadata on responses andget_last_rate_limit().
- Client: Parse RFC5988 Link headers and surface
_metadata.nextLink/_metadata.prevLink. - Client: Support absolute URL GET via
get_absolute(url)and auto-detect absolute endpoints. - People:
iter_peopleprefersnextLink, falls back tonexttoken or offset. - People:
list_people_next(next_link)helper to fetch subsequent pages.
- Removed appointment test log file logging
- Updated website URL to https://theperry.group
- Initial release
- Complete API coverage for all Follow Up Boss endpoints
- Full type safety with comprehensive type hints
- Comprehensive test suite
- Documentation and examples
- Follow Up Boss API Documentation
- Follow Up Boss - The official Follow Up Boss platform
Made with ❤️ by The Perry Group