
    	/iC                     x   d Z ddlZddlZddlmZmZmZmZ ddlZ ej                  e
      ZdZ G d de      Z G d d      Zd	efd
Ze
dk(  rddlZ ej$                  ej&                          e       Z eej,                        dk  rB ed        ed        ed        ed        ed        ed        ej0                  d       ej,                  d   Zedk(  r-ej5                  d      Z e ej8                  ed             yedk(  rh eej,                        dk  r ed        ej0                  d       ej;                  ej,                  d         Z e ej8                  ed             yedk(  r-ej=                  d      Z e ej8                  ed             yedk(  ri eej,                        dk  r ed        ej0                  d       ej?                  ej,                  d         Z e ej8                  ed             y ede         ej0                  d       yy) z
Instantly.ai API V2 Client
==========================
Python wrapper for the Instantly.ai cold email platform API V2.

API Docs: https://developer.instantly.ai/api/v2
Auth: Bearer token
Base URL: https://api.instantly.ai/api/v2

Created: 2026-02-17
    N)AnyDictListOptionalzhttps://api.instantly.ai/api/v2c                   2     e Zd ZdZddededef fdZ xZS )InstantlyAPIErrorz/Raised when the Instantly API returns an error.status_codemessageresponse_bodyc                 Z    || _         || _        || _        t        |   d| d|        y )NzInstantly API Error : )r	   r
   r   super__init__)selfr	   r
   r   	__class__s       ./mnt/e/genesis-system/core/instantly_client.pyr   zInstantlyAPIError.__init__   s5    &*/}BwiHI    )N)	__name__
__module____qualname____doc__intstrr   r   __classcell__)r   s   @r   r   r      s*    9JC J# Jc J Jr   r   c                      e Zd ZdZd2dedefdZ	 	 	 d3dededee   d	ee   d
ee   de	fdZ
	 	 	 	 d4dedee   dee   dee   def
dZdedefdZ	 	 d5dedeee      dee   defdZdedefdZdedefdZdedefdZdedefdZ	 	 	 	 	 	 	 	 	 d6dedee   dee   dee   dee   d ee   d!ee   d"ee   d#ee   d$eeee	f      defd%Z	 	 	 	 d7d&ee   dee   dee   d'ed(edefd)Z	 	 	 	 	 d8dee   dee   dedee   dee   defd*Zd+edefd,Zd+edefd-Zd+edefd.Z	 	 	 d9dee   d+ee   dedefd/Zd:dedefd0Zdedefd1Zy);InstantlyClientz
    Client for the Instantly.ai API V2.

    Usage:
        client = InstantlyClient(api_key="your_api_key_here")
        campaigns = client.list_campaigns()
        client.create_lead(campaign_id="...", email="...", first_name="...")
    api_keytimeoutc                     || _         || _        t        j                         | _        | j                  j
                  j                  d| ddd       y)z
        Initialize the Instantly API client.

        Args:
            api_key: Instantly API V2 key (Bearer token)
            timeout: Request timeout in seconds
        zBearer zapplication/json)AuthorizationzContent-TypeAcceptN)r   r   requestsSessionsessionheadersupdate)r   r   r   s      r   r   zInstantlyClient.__init__-   sO     '')##&wi0.(%
 	r   Nmethodendpointparams	json_dataextra_timeoutreturnc           	         t          | }|xs | j                  }t        j                  d| d|        |r-t        j                  dt	        j
                  |d              	 | j                  j                  |||||      }|j                  j                  d      }
|j                  j                  d      }|
t        j                  d|
 d| d       |j                  dk\  rn	 |j	                         }|}t'        |t(              r+|j                  d|j                  dt+        |                  }t        |j                  t+        |      |      |j                  dk(  s|j$                  j-                         sd|j                  dS 	 |j	                         S # t        j                  j                  $ r t        dd| d	| d
      t        j                  j                  $ r}	t        dd| d|	       d}	~	ww xY w# t        j                   t"        f$ r |j$                  }Y 4w xY w# t        j                   t"        f$ r |j$                  |j                  dcY S w xY w)a  
        Make a request to the Instantly API.

        Args:
            method: HTTP method (GET, POST, PATCH, DELETE)
            endpoint: API endpoint path (e.g., "/campaigns")
            params: Query parameters
            json_data: JSON request body
            extra_timeout: Override default timeout

        Returns:
            Parsed JSON response

        Raises:
            InstantlyAPIError: If the API returns an error
        zInstantly API  z  Body:    indent)r'   urlr)   jsonr   i  zRequest to z timed out after sr   zConnection error to r   NzX-RateLimit-RemainingzX-RateLimit-Limitz  Rate limit: /z
 remainingi  r
   error   success)statusr	   )raw_textr	   )BASE_URLr   loggerdebugr3   dumpsr$   requestr"   
exceptionsTimeoutr   ConnectionErrorr%   getr	   JSONDecodeError
ValueErrortext
isinstancedictr   strip)r   r'   r(   r)   r*   r+   r2   r   responseerate_remaining
rate_limit
error_body	error_msgs                 r   _requestzInstantlyClient._request>   sI   0 
8*%/4<<~fXQse45LL8DJJy$C#DEF	J||++ , H "))--.EF%%))*=>
%LL>.)9:,jQR3&+%]]_
 #I*d+&NN9jnnWcR\o6^_	#H$8$8#i.*UU 3&hmm.A.A.C'8L8LMM	T==?"9 ""** 	Y#C;se;LWIUV)WXX""22 	J#A)=cU"QC'HII	J ((*5 +%]]
+ $$j1 	T (h>R>RSS	Ts=   & F  *H H2  AH,G>>H%H/.H/22I'&I'limitr9   searchstarting_afterc                 \    d|i}|||d<   |r||d<   |r||d<   | j                  dd|      S )a|  
        List campaigns.

        GET /campaigns

        Args:
            limit: Number of campaigns to return (default 10)
            status: Filter by status (0=draft, 1=active, 2=paused, 3=completed)
            search: Search campaigns by name
            starting_after: Pagination cursor

        Returns:
            Dict with campaigns list and pagination info
        rQ   r9   rR   rS   GET
/campaignsr)   rP   )r   rQ   r9   rR   rS   r)   s         r   list_campaignszInstantlyClient.list_campaigns   sN    * 5!%F8%F8'5F#$}}UL}@@r   campaign_idc                 ,    | j                  dd|       S )z
        Get a specific campaign by ID.

        GET /campaigns/{id}

        Args:
            campaign_id: The campaign UUID

        Returns:
            Campaign details dict
        rU   /campaigns/rX   r   rZ   s     r   get_campaignzInstantlyClient.get_campaign   s     }}Uk+$?@@r   name	sequencescampaign_schedulec                 N    d|i}|r||d<   |r||d<   | j                  dd|      S )a+  
        Create a new campaign.

        POST /campaigns

        Args:
            name: Campaign name
            sequences: List of sequence objects with steps and variants.
                       Only the first element is used. Format:
                       [{"steps": [{"type": "email", "delay": 0,
                                    "variants": [{"subject": "...", "body": "..."}]}]}]
            campaign_schedule: Schedule config with timezone, start_date, end_date, schedules

        Returns:
            Created campaign dict with id
        r_   r`   ra   POSTrV   r*   rX   )r   r_   r`   ra   bodys        r   create_campaignzInstantlyClient.create_campaign   s=    , !'~ )D(9D$%}}V\T}BBr   c                 0    | j                  dd| |      S )z
        Update a campaign.

        PATCH /campaigns/{id}

        Args:
            campaign_id: Campaign UUID
            **kwargs: Fields to update (name, sequences, campaign_schedule, etc.)

        Returns:
            Updated campaign dict
        PATCHr\   rd   rX   )r   rZ   kwargss      r   update_campaignzInstantlyClient.update_campaign   s      }}WK=&AV}TTr   c                 2    | j                  dd| di       S )z
        Activate (start) a campaign.

        POST /campaigns/{id}/activate

        Args:
            campaign_id: Campaign UUID

        Returns:
            Activation result
        rc   r\   z	/activaterd   rX   r]   s     r   activate_campaignz!InstantlyClient.activate_campaign   s#     }}V{;-y%IUW}XXr   c                 2    | j                  dd| di       S )z
        Pause a campaign.

        POST /campaigns/{id}/pause

        Args:
            campaign_id: Campaign UUID

        Returns:
            Pause result
        rc   r\   z/pauserd   rX   r]   s     r   pause_campaignzInstantlyClient.pause_campaign   s#     }}V{;-v%FRT}UUr   c                 ,    | j                  dd|       S )z
        Delete a campaign. WARNING: This is permanent.

        DELETE /campaigns/{id}

        Args:
            campaign_id: Campaign UUID

        Returns:
            Deletion result
        DELETEr\   rX   r]   s     r   delete_campaignzInstantlyClient.delete_campaign   s     }}X[M'BCCr   emaillist_id
first_name	last_namecompany_namewebsitephonepersonalizationcustom_variablesc                     d|i}|r||d<   |r||d<   |r||d<   |r||d<   |r||d<   |r||d<   |r||d<   |	r|	|d	<   |
r|
|d
<   | j                  dd|      S )a  
        Create a single lead.

        POST /leads

        Args:
            email: Lead email address (required)
            campaign_id: Campaign UUID to add lead to
            list_id: Lead list UUID to add lead to
            first_name: Lead first name
            last_name: Lead last name
            company_name: Lead company name
            website: Lead website URL
            phone: Lead phone number
            personalization: Custom personalization text
            custom_variables: Dict of custom variable key-value pairs.
                             Values must be string, number, boolean, or null.
                             Objects/arrays are NOT allowed.

        Returns:
            Created lead dict
        rr   rZ   rs   rt   ru   rv   rw   rx   ry   rz   rc   z/leadsrd   rX   )r   rr   rZ   rs   rt   ru   rv   rw   rx   ry   rz   re   s               r   create_leadzInstantlyClient.create_lead  s    F !(/"-D%DO!+D )D#/D %DO!DM&5D"#'7D#$}}VX}>>r   leadsskip_if_in_workspaceskip_if_in_campaignc                 T    |||d}|r||d<   |r||d<   | j                  dd|d      S )a  
        Add leads in bulk to a campaign or list.

        POST /leads/batch

        Args:
            leads: List of lead dicts (max 1000). Each must have at minimum 'email'.
            campaign_id: Campaign UUID to add leads to
            list_id: Lead list UUID to add leads to
            skip_if_in_workspace: Skip if lead exists in any workspace campaign
            skip_if_in_campaign: Skip if already in the target campaign (default True)

        Returns:
            Bulk operation result
        )r}   r~   r   rZ   rs   rc   z/leads/batch<   )r*   r+   rX   )r   r}   rZ   rs   r~   r   re   s          r   add_leads_bulkzInstantlyClient.add_leads_bulkL  sG    0 $8#6 
 "-D%DO}}V^tSU}VVr   c                 j    d|i}|r||d<   |r||d<   |r||d<   |r||d<   | j                  dd|      S )	a  
        List leads with optional filters.

        GET /leads/search

        NOTE: The V2 API uses /leads/search as the list endpoint, not /leads.
        This endpoint requires at least a campaign_id or list_id.

        Args:
            campaign_id: Filter by campaign
            list_id: Filter by lead list
            limit: Number of results
            email: Filter by email address
            starting_after: Pagination cursor

        Returns:
            Dict with leads list
        rQ   rZ   rs   rr   rS   rU   z/leads/searchrW   rX   )r   rZ   rs   rQ   rr   rS   r)   s          r   
list_leadszInstantlyClient.list_leadsp  sW    4 #*5!1$/F=! 'F9#F7O'5F#$}}UOF}CCr   lead_idc                 ,    | j                  dd|       S )z
        Get a specific lead by ID.

        GET /leads/{id}

        Args:
            lead_id: Lead UUID

        Returns:
            Lead details dict
        rU   /leads/rX   r   r   s     r   get_leadzInstantlyClient.get_lead  s     }}UggY$788r   c                 0    | j                  dd| |      S )z
        Update a lead.

        PATCH /leads/{id}

        Args:
            lead_id: Lead UUID
            **kwargs: Fields to update

        Returns:
            Updated lead dict
        rh   r   rd   rX   )r   r   ri   s      r   update_leadzInstantlyClient.update_lead  s      }}Wy&9V}LLr   c                 ,    | j                  dd|       S )z
        Delete a lead. WARNING: This is permanent.

        DELETE /leads/{id}

        Args:
            lead_id: Lead UUID

        Returns:
            Deletion result
        rp   r   rX   r   s     r   delete_leadzInstantlyClient.delete_lead  s     }}X	':;;r   c                 N    d|i}|r||d<   |r||d<   | j                  dd|      S )z
        List sent/received emails.

        GET /emails

        Args:
            campaign_id: Filter by campaign
            lead_id: Filter by lead
            limit: Number of results

        Returns:
            Dict with emails list
        rQ   rZ   r   rU   z/emailsrW   rX   )r   rZ   r   rQ   r)   s        r   list_emailszInstantlyClient.list_emails  s=    & #*5!1$/F=! 'F9}}UIf}==r   c                 .    | j                  ddd|i      S )z
        List sending accounts.

        GET /accounts

        Args:
            limit: Number of results

        Returns:
            Dict with accounts list
        rU   z	/accountsrQ   rW   rX   )r   rQ   s     r   list_accountszInstantlyClient.list_accounts  s     }}UK%8H}IIr   c                 ,    | j                  dd|       S )a  
        Get campaigns associated with a sending account email.

        GET /account-campaign-mappings/{email}

        NOTE: The V2 API only supports GET for account-campaign mappings.
        Assigning accounts to campaigns must be done via the Instantly dashboard.

        Args:
            email: Sending account email address

        Returns:
            List of campaign mappings for this account
        rU   z/account-campaign-mappings/rX   )r   rr   s     r   get_account_campaign_mappingsz-InstantlyClient.get_account_campaign_mappings  s     }}U&A%$IJJr   )   )NNN)
   NNN)NN)	NNNNNNNNN)NNFT)NNr   NN)NNr   )r   )r   r   r   r   r   r   r   r   r   r   rP   rY   r^   r   rf   rj   rl   rn   rq   r|   boolr   r   r   r   r   r   r   r    r   r   r   r   #   s    c * "&$('+ETET ET 	ET
 D>ET  }ET 
ETZ  $ $(,AA A 	A
 !A 
A>A A A" +/,0	CC DJ'C $D>	C
 
C>U3 UT UYS YT YV# V$ VD3 D4 D* &*!%$(#'&*!%#)-598?8? c]8? #	8?
 SM8? C=8? sm8? #8? }8? "#8? #4S>28? 
8?z &*!%%*$("WDz"W c]"W #	"W
 #"W ""W 
"WL &*!%#(,$Dc]$D #$D 	$D
 }$D !$D 
$DL9 9 9M3 MT M<3 <4 <( &*!%	>c]> #> 	>
 
>>J3 J J$K3 K4 Kr   r   r,   c                      d} t        |       S )zn
    Create a client using the Genesis API key.

    Returns:
        Configured InstantlyClient instance
    zDMjBjODUxNGYtNjA5MC00NjY4LWFhY2UtOWZmYTE3NDhhMmQ1OnRTYkpqRU94RHJveg==)r   )r   )API_KEYs    r   get_default_clientr     s     UG7++r   __main__)levelr/   z5Usage: python instantly_client.py <command> [args...]z	Commands:z*  list-campaigns        List all campaignsz,  get-campaign <id>     Get campaign detailsz-  list-accounts         List sending accountsz.  list-leads <campaign> List leads in campaign   zlist-campaigns   )rQ   r0   zget-campaign   zError: campaign_id requiredzlist-accountsz
list-leads)rZ   zUnknown command: ) r   r3   loggingtypingr   r   r   r   r"   	getLoggerr   r<   r;   	Exceptionr   r   r   sysbasicConfigINFOclientlenargvprintexitcommandrY   resultr>   r^   r   r   r   r   r   <module>r      s$  
   , , 			8	$ -J	 JbK bKJ,O , zGgll+!F
388}qEFk:;<==>>?hhqkG""&&R&0jdjj*+	N	"sxx=1/0CHHQK$$SXXa[1jdjj*+	O	#%%B%/jdjj*+	L	 sxx=1/0CHHQK""sxx{";jdjj*+ 	!'+,U r   