EnrichB2B MCP Server

by moonlabsai
Verified
""" EnrichB2B API Client This module provides a Python client for the EnrichB2B API, which validates whether a lead still works at a company with real-time lookups against their LinkedIn profile and returns a validation score. The API provides endpoints to search leads and companies on LinkedIn and get fresh data. Both single and bulk endpoints are available to fit various business needs. """ import json import requests from typing import Dict, List, Optional, Union, Any from dotenv import load_dotenv import os import random load_dotenv() ENRICHB2B_API_KEY = os.getenv("ENRICHB2B_API_KEY") class EnrichB2BConfig: """Configuration for EnrichB2B API client.""" def __init__(self, api_key: str, base_url: str = "https://api.enrichb2bdata.com"): """ Initialize EnrichB2B configuration. Args: api_key: Your EnrichB2B API key base_url: The base URL for the API (defaults to https://api.enrichb2bdata.com) """ self.api_key = api_key self.base_url = base_url.rstrip('/') def get_headers(self) -> Dict[str, str]: """Get headers required for API requests.""" return { "X-EB2B-API-Key": self.api_key, "Content-Type": "application/json" } class ContactRequest: """Represents a request to search for a contact on LinkedIn.""" def __init__( self, request_id: Optional[str] = None, linkedin_url: Optional[str] = None, first_name: Optional[str] = None, last_name: Optional[str] = None, company: Optional[str] = None, job_title: Optional[str] = None ): """ Initialize a contact search request. Args: request_id: Optional ID to track this request linkedin_url: LinkedIn profile URL of the contact first_name: First name of the contact (required if linkedin_url not provided) last_name: Last name of the contact (required if linkedin_url not provided) company: Company name where the contact works (required if linkedin_url not provided) job_title: Job title of the contact (optional, but improves search accuracy) """ self.request_id = request_id self.linkedin_url = linkedin_url self.first_name = first_name self.last_name = last_name self.company = company self.job_title = job_title def to_dict(self) -> Dict[str, Any]: """Convert request to dictionary for API submission.""" return {k: v for k, v in self.__dict__.items() if v is not None} class CompanyRequest: """Represents a request to search for a company on LinkedIn.""" def __init__( self, request_id: Optional[str] = None, linkedin_url: Optional[str] = None, name: Optional[str] = None ): """ Initialize a company search request. Args: request_id: Optional ID to track this request linkedin_url: LinkedIn URL of the company name: Name of the company """ self.request_id = request_id self.linkedin_url = linkedin_url self.name = name def to_dict(self) -> Dict[str, Any]: """Convert request to dictionary for API submission.""" return {k: v for k, v in self.__dict__.items() if v is not None} class CompanyEmployeeRequest: """Represents a request to search for employees of a company.""" def __init__( self, company_linkedin_url: str, search_name: Optional[str] = None, page_size: int = 50, offset: int = 0 ): """ Initialize a company employees search request. Args: company_linkedin_url: LinkedIn URL of the company (required) search_name: Optional name to track this search page_size: Number of results per page (10-50) offset: Offset for pagination (starts at 0) """ self.company_linkedin_url = company_linkedin_url self.search_name = search_name self.page_size = min(max(page_size, 10), 50) # Ensure between 10-50 self.offset = max(offset, 0) # Ensure non-negative def to_dict(self) -> Dict[str, Any]: """Convert request to dictionary for API submission.""" return {k: v for k, v in self.__dict__.items() if v is not None} class ContactActivitiesRequest: """Represents a request to search for activities of a contact.""" def __init__( self, linkedin_url: str, request_id: Optional[str] = None, linkedin_profile_uid: Optional[str] = None, search_name: Optional[str] = None, how_many_pages: int = 1, how_many_pages_comments_per_post: Optional[int] = None, how_many_pages_likes_per_post: Optional[int] = None ): """ Initialize a contact activities search request. Args: linkedin_url: LinkedIn URL of the contact (required) request_id: Optional ID to track this request linkedin_profile_uid: Contact UID on LinkedIn search_name: Optional name to track this search how_many_pages: Number of pages to fetch (1-50) how_many_pages_comments_per_post: Pages of comments to fetch per post (0-50) how_many_pages_likes_per_post: Pages of likes to fetch per post (0-50) """ self.linkedin_url = linkedin_url # Generate a random 8-digit request ID if none provided self.request_id = request_id if request_id else str(random.randint(10000000, 99999999)) self.linkedin_profile_uid = linkedin_profile_uid self.search_name = search_name self.how_many_pages = min(max(how_many_pages, 1), 50) # Ensure between 1-50 if how_many_pages_comments_per_post is not None: self.how_many_pages_comments_per_post = min(max(how_many_pages_comments_per_post, 0), 50) else: self.how_many_pages_comments_per_post = None if how_many_pages_likes_per_post is not None: self.how_many_pages_likes_per_post = min(max(how_many_pages_likes_per_post, 0), 50) else: self.how_many_pages_likes_per_post = None def to_dict(self) -> Dict[str, Any]: """Convert request to dictionary for API submission.""" return {k: v for k, v in self.__dict__.items() if v is not None} class EnrichB2BClient: """Client for the EnrichB2B API.""" def __init__(self, config: EnrichB2BConfig): """ Initialize the EnrichB2B API client. Args: config: Configuration object with API key and base URL """ self.config = config def _make_request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Dict: """ Make a request to the EnrichB2B API. Args: method: HTTP method (GET, POST, etc.) endpoint: API endpoint data: Request data (for POST requests) Returns: Dict: API response as a dictionary Raises: requests.exceptions.RequestException: If the request fails ValueError: If the response is not valid JSON """ url = f"{self.config.base_url}{endpoint}" headers = self.config.get_headers() response = requests.request(method, url, headers=headers, json=data) # Raise exception for HTTP errors response.raise_for_status() return response.json() # Contact endpoints def search_contact( self, request: ContactRequest, extended_search: Optional[bool] = None, include_company_details: Optional[bool] = None, include_followers_count: Optional[bool] = None, search_name: Optional[str] = None ) -> Dict: """ Search for a contact on LinkedIn. Args: request: Contact search request extended_search: Whether to use extended search include_company_details: Whether to include company details include_followers_count: Whether to include followers count search_name: Optional name to track this search Returns: Dict: API response """ data = { **request.to_dict() } if extended_search is not None: data["extended_search"] = extended_search if include_company_details is not None: data["include_company_details"] = include_company_details if include_followers_count is not None: data["include_followers_count"] = include_followers_count if search_name is not None: data["search_name"] = search_name return self._make_request("POST", "/v1/search/contact", data) def search_contact_bulk( self, requests: List[ContactRequest], extended_search: Optional[bool] = None, include_company_details: Optional[bool] = None, include_followers_count: Optional[bool] = None, search_name: Optional[str] = None ) -> Dict: """ Search for multiple contacts on LinkedIn in bulk. Args: requests: List of contact search requests extended_search: Whether to use extended search include_company_details: Whether to include company details include_followers_count: Whether to include followers count search_name: Optional name to track this search Returns: Dict: API response """ data = { "request": [req.to_dict() for req in requests] } if extended_search is not None: data["extended_search"] = extended_search if include_company_details is not None: data["include_company_details"] = include_company_details if include_followers_count is not None: data["include_followers_count"] = include_followers_count if search_name is not None: data["search_name"] = search_name return self._make_request("POST", "/v1/search/contact/bulk", data) # Company endpoints def search_company( self, request: CompanyRequest, extended_search: Optional[bool] = None, search_name: Optional[str] = None ) -> Dict: """ Search for a company on LinkedIn. Args: request: Company search request extended_search: Whether to use extended search search_name: Optional name to track this search Returns: Dict: API response """ data = { **request.to_dict() } if extended_search is not None: data["extended_search"] = extended_search if search_name is not None: data["search_name"] = search_name return self._make_request("POST", "/v1/search/company", data) def search_company_bulk( self, requests: List[CompanyRequest], extended_search: Optional[bool] = None, search_name: Optional[str] = None ) -> Dict: """ Search for multiple companies on LinkedIn in bulk. Args: requests: List of company search requests extended_search: Whether to use extended search search_name: Optional name to track this search Returns: Dict: API response """ data = { "request": [req.to_dict() for req in requests] } if extended_search is not None: data["extended_search"] = extended_search if search_name is not None: data["search_name"] = search_name return self._make_request("POST", "/v1/search/company/bulk", data) def search_company_employees(self, request: CompanyEmployeeRequest) -> Dict: """ Search for employees of a company on LinkedIn. Args: request: Company employees search request Returns: Dict: API response """ return self._make_request("POST", "/v1/search/company_employees", request.to_dict()) # Contact activities endpoints def search_contact_activities(self, request: ContactActivitiesRequest) -> Dict: """ Search for activities of a contact on LinkedIn. Args: request: Contact activities search request Returns: Dict: API response """ return self._make_request("POST", "/v1/search/contact/activities", request.to_dict()) def search_contact_activities_bulk( self, requests: List[ContactActivitiesRequest], how_many_pages: int = 1, how_many_pages_comments_per_post: Optional[int] = None, how_many_pages_likes_per_post: Optional[int] = None, search_name: Optional[str] = None ) -> Dict: """ Search for activities of multiple contacts on LinkedIn in bulk. Args: requests: List of contact post requests how_many_pages: Number of pages to fetch (1-10) how_many_pages_comments_per_post: Pages of comments to fetch per post (0-50) how_many_pages_likes_per_post: Pages of likes to fetch per post (0-50) search_name: Optional name to track this search Returns: Dict: API response """ data = { "request": [ { "linkedin_url": req.linkedin_url, "request_id": req.request_id, "linkedin_profile_uid": req.linkedin_profile_uid } for req in requests ], "how_many_pages": min(max(how_many_pages, 1), 10) # Ensure between 1-10 } if how_many_pages_comments_per_post is not None: data["how_many_pages_comments_per_post"] = min(max(how_many_pages_comments_per_post, 0), 50) if how_many_pages_likes_per_post is not None: data["how_many_pages_likes_per_post"] = min(max(how_many_pages_likes_per_post, 0), 50) if search_name is not None: data["search_name"] = search_name return self._make_request("POST", "/v1/search/contact/activities/bulk", data) def get_post_interactions( self, activity_id: str, fetch_type: str, page: int = 1, limit: int = 10 ) -> Dict: """ Get interactions on a post. Args: activity_id: ID of the activity (post) fetch_type: Type of interactions to fetch (comments, likers, empathy, entertainment, praise, interest, maybe, funny) page: Page number (default 1) limit: Number of results per page (default 10) Returns: Dict: API response """ params = { "activity_id": activity_id, "fetch_type": fetch_type, "page": page, "limit": limit } # Convert params to query string manually query_string = "&".join(f"{k}={v}" for k, v in params.items()) endpoint = f"/v1/search/contact/activities/post-interactions?{query_string}" return self._make_request("GET", endpoint) def get_profile_comments( self, profile_id: str, next_page_token: Optional[str] = None, limit: int = 10 ) -> Dict: """ Get comments on a profile. Args: profile_id: ID of the profile next_page_token: Token for pagination limit: Number of results per page (default 10) Returns: Dict: API response """ params = { "profile_id": profile_id, "limit": limit } if next_page_token: params["next_page_token"] = next_page_token # Convert params to query string manually query_string = "&".join(f"{k}={v}" for k, v in params.items()) endpoint = f"/v1/search/contact/activities/profile-comments?{query_string}" return self._make_request("GET", endpoint) # Usage examples def example_usage(): """Example usage of the EnrichB2B API client.""" # Initialize the client config = EnrichB2BConfig(ENRICHB2B_API_KEY) client = EnrichB2BClient(config) # # Example 1: Search for a contact by LinkedIn URL # contact_req = ContactRequest( # linkedin_url="https://www.linkedin.com/in/davidstubbss" # ) # contact_result = client.search_contact(contact_req) # print("Contact search result:", json.dumps(contact_result, indent=2)) # Example 4: Search for activities of a contact activities_req = ContactActivitiesRequest( linkedin_url="https://www.linkedin.com/in/davidstubbss", how_many_pages=1, how_many_pages_comments_per_post=1 ) activities_result = client.search_contact_activities(activities_req) print("Contact activities result:", json.dumps(activities_result, indent=2))