import logging import os import sys import requests logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler()], ) logger = logging.getLogger(__name__) class FolkAPI: BASE_URL = "https://api.folk.app/v1" def __init__(self, api_key: str): api_key = os.environ.get("FOLK_API_KEY", api_key) self.headers = {"Authorization": f"Bearer {api_key}"} logger.info(f"FolkAPI initialized with API key: {api_key[:4]}***") def get_groups(self): """Fetch all groups from Folk.""" url = f"{self.BASE_URL}/groups" response = requests.get(url, headers=self.headers) response.raise_for_status() return response.json() def create_company( self, name: str, group_id: str = None, website: str = None, linkedin_url: str = None, description: str = None, emails=None, phones=None, addresses=None, urls=None, custom_field_values=None, groups=None, **kwargs, ): """Create a company (investor) in a specific group. This method builds a payload matching Folk's Create Company API: https://developer.folk.app/api-reference/companies/create-a-company It keeps backward compatibility with the previous `group_id`, `website` and `linkedin_url` arguments. """ url = f"{self.BASE_URL}/companies" # Build the top-level payload expected by Folk data = {"name": name} if description: data["description"] = description # Groups: prefer explicit `groups`, else fall back to `group_id` if groups: # Accept either list of ids or list of dicts formatted = [] for g in groups: if isinstance(g, dict) and g.get("id"): formatted.append({"id": g["id"]}) else: formatted.append({"id": str(g)}) data["groups"] = formatted elif group_id: data["groups"] = [{"id": group_id}] # Helper to normalize single or multiple inputs into lists def _to_list(val): if val is None: return None if isinstance(val, (list, tuple)): return [v for v in val if v is not None] return [val] # URLs: include website and linkedin_url if provided and merge with urls urls_list = _to_list(urls) or [] if website: urls_list.append(website) if linkedin_url: urls_list.append(linkedin_url) if urls_list: data["urls"] = urls_list # Emails/phones/addresses emails_list = _to_list(emails) if emails_list: data["emails"] = emails_list phones_list = _to_list(phones) if phones_list: data["phones"] = phones_list addresses_list = _to_list(addresses) if addresses_list: data["addresses"] = addresses_list # Custom field values follow the API's structure if custom_field_values: data["customFieldValues"] = custom_field_values # Allow passing any additional top-level fields via kwargs (careful) for k, v in kwargs.items(): # don't overwrite keys we explicitly set if k not in data: data[k] = v response = requests.post(url, headers=self.headers, json=data) response.raise_for_status() return response.json() def create_person( self, first_name: str, last_name: str, email: str = None, company_id: str = None, group_id: str = None, linkedin_url: str = None, companies=None, emails=None, phones=None, addresses=None, urls=None, custom_field_values=None, groups=None, **kwargs, ): """Create a person in the workspace. Builds payload matching Folk's Create Person API: use camelCase keys (firstName, lastName, groups, companies, emails, etc.). Keeps backward compatibility with `company_id` and `group_id`. """ url = f"{self.BASE_URL}/people" data = {"firstName": first_name, "lastName": last_name} # Groups: explicit `groups` preferred, else fallback to `group_id` if groups: formatted = [] for g in groups: if isinstance(g, dict) and g.get("id"): formatted.append({"id": g["id"]}) else: formatted.append({"id": str(g)}) data["groups"] = formatted elif group_id: data["groups"] = [{"id": group_id}] # Companies: keep backward compatibility with company_id if companies: formatted = [] for c in companies: if isinstance(c, dict): formatted.append(c) elif isinstance(c, str): # treat as id formatted.append({"id": c}) if formatted: data["companies"] = formatted elif company_id: data["companies"] = [{"id": company_id}] # Helper to normalize into lists def _to_list(val): if val is None: return None if isinstance(val, (list, tuple)): return [v for v in val if v is not None] return [val] emails_list = _to_list(emails) or [] if email: emails_list.insert(0, email) if emails_list: data["emails"] = emails_list phones_list = _to_list(phones) if phones_list: data["phones"] = phones_list addresses_list = _to_list(addresses) if addresses_list: data["addresses"] = addresses_list urls_list = _to_list(urls) or [] if linkedin_url: urls_list.append(linkedin_url) if urls_list: data["urls"] = urls_list if custom_field_values: data["customFieldValues"] = custom_field_values # Allow passthrough of other top-level fields in kwargs for k, v in kwargs.items(): if k not in data: data[k] = v response = requests.post(url, headers=self.headers, json=data) response.raise_for_status() return response.json()