Python API Client for Close

Overview

Close API

PyPI version CircleCI

A convenient Python wrapper for the Close API.

Installation

pip install closeio

Sample Usage (of API client)

from closeio_api import Client

api = Client('YOUR_API_KEY')

# post a lead
lead = api.post('lead', data={'name': 'New Lead'})

# get 5 most recently updated opportunities
opportunities = api.get('opportunity', params={'_order_by': '-date_updated', '_limit': 5})

# fetch multiple leads (using search syntax)
lead_results = api.get('lead', params={
    '_limit': 10,
    '_fields': 'id,display_name,status_label',
    'query': 'custom.my_custom_field:"some_value" status:"Potential" sort:updated'
})

Example scripts

Check out https://github.com/closeio/closeio-api-scripts for helpful scripts already written to accomplish some common tasks.

Other Languages

There are unofficial API clients available in other languages too, thanks to some awesome contributors:

Comments
  • bulk_update_lead_info for adding a contact doesn't appear to work

    bulk_update_lead_info for adding a contact doesn't appear to work

    (closeio)--- Projects/closeio-api ‹master» python scripts/bulk_update_leads_info.py ~/Downloads/test.csv --api-key <redacted> --confirmed
    [2015-05-15 18:59:23,018] INFO Starting new HTTPS connection (1): app.close.io
    [2015-05-15 18:59:23,539] INFO line 2 updated: lead_iyTYlkzfVWKn0rH0x1yGrPX2vMOzzCsOdBK205WrzLk Plumbing Medic
    [2015-05-15 18:59:23,539] INFO summary: updated[1], new[0], skipped[0]
    (closeio)--- Projects/closeio-api ‹master» cat ~/Downloads/test.csv
    lead_id,contact_name,contact_phone,,,,,,
    lead_iyTYlkzfVWKn0rH0x1yGrPX2vMOzzCsOdBK205WrzLk,Conference Call Line,+1818-452-3980,,,,,,
    

    No contact added to https://app.close.io/lead/lead_iyTYlkzfVWKn0rH0x1yGrPX2vMOzzCsOdBK205WrzLk/

    bug 
    opened by anemitz 10
  • Clean up the code, add docstrings, and get rid of the poorly implemented async client

    Clean up the code, add docstrings, and get rid of the poorly implemented async client

    Right now the async client makes the code more complex and doesn't really add much value. I doubt it's used in the wild and if it is, then the usage is probably quite confusing and hacky. I think we might as well get rid of it.

    Examples of current confusing behavor:

    • api.get/post/put/delete is still synchronous
    • api.map expects a list of grequests.AsyncRequest, but there's no easy way to construct them.

    This PR introduces breaking changes and should be published along with a major version bump.

    TODO:

    • [x] Add mocked unit tests
    • [x] Bump version to v1.0
    • [x] Add a GitHub Release and publish on PyPI
    opened by wojcikstefan 8
  • Issue 59 - fix user reassign

    Issue 59 - fix user reassign

    Fixes #59

    All the changes are supported by TDD with functional tests hitting the live API.

    I'm not committing the tests but I can share them somewhere in case you want to have a look.

    /cc @thomasst

    opened by flevour 7
  • datetimes do not take timezone offset into account properly.

    datetimes do not take timezone offset into account properly.

    As an example, today I ran this request:

    resp = api.get('report/activity/ORGID', params=
    {'user_id':'USERID', 'date_start':'2018-01-02', 'date_end':'2018-01-02'})
    

    The response I got back was:

    {
        "revenue_created_annual_created": 0, 
        "opportunities_created": 0, 
        "revenue_lost_annual_created": 0, 
        "sms_sent": 0, 
        "leads_created": 0, 
        "opportunities_lost": 0, 
        "revenue_won_monthly": 0, 
        "revenue_won_annual": 0, 
        "leads_contacted": 3, 
        "revenue_won_one_time": 0, 
        "revenue_lost_annual": 0, 
        "revenue_lost_one_time_created": 0, 
        "revenue_lost_one_time": 0, 
        "revenue_created_monthly": 0, 
        "revenue_won_annual_created": 0, 
        "opportunities_won": 0, 
        "opportunities_created_created": 0, 
        "emails_sent": 0, 
        "revenue_created_monthly_created": 0, 
        "emails_received": 0, 
        "calls_duration_total": 0, 
        "sms_received": 0, 
        "calls_duration_average": 0, 
        "revenue_won_one_time_created": 0, 
        "revenue_created_one_time": 0, 
        "revenue_won_monthly_created": 0, 
        "revenue_lost_monthly_created": 0, 
        "opportunities_won_created": 0, 
        "opportunities_lost_created": 0, 
        "revenue_lost_monthly": 0, 
        "_queries": {
            "leads_contacted": "call(duration > 0  date >= 2018-01-01 date <= 2018-01-01) or email(direction:sent  date >= 2018-01-01 date <= 2018-01-01) or sms(direction:sent  date >= 2018-01-01 date <= 2018-01-01)", 
            "emails_received": "email(direction:received  date >= 2018-01-01 date <= 2018-01-01)", 
            "calls": "call( date >= 2018-01-01 date <= 2018-01-01)", 
            "opportunities_created": "opportunity( created >= 2018-01-01 created <= 2018-01-01)", 
            "opportunities_won_created": "opportunity(status_type:won  closed >= 2018-01-01 closed <= 2018-01-01)", 
            "opportunities_lost_created": "opportunity(status_type:lost  lost >= 2018-01-01 lost <= 2018-01-01)", 
            "sms_sent": "sms(direction:sent  date >= 2018-01-01 date <= 2018-01-01)", 
            "leads_created": " created >= 2018-01-01 created <= 2018-01-01", 
            "opportunities_won": "opportunity(status_type:won  closed >= 2018-01-01 closed <= 2018-01-01)", 
            "opportunities_created_created": "opportunity( created >= 2018-01-01 created <= 2018-01-01)", 
            "emails_sent": "email(direction:sent  date >= 2018-01-01 date <= 2018-01-01)", 
            "sms_received": "smsdate >= 2018-01-01 date <= 2018-01-01)", 
            "opportunities_lost": "opportunity(status_type:lost" lost >= 2018-01-01 lost <= 2018-01-01)"
        }, 
        "revenue_created_one_time_created": 0, 
        "calls": 0, 
        "revenue_created_annual": 0
    }
    
    

    since the bounding dates are 2018-01-01 and 2018-01-01, it means that we aren't correctly factoring in timezone when passing through date params.

    We need to add the tz.offset*-1 to the datetime in both directions for it to appear the same as it does in app.

    opened by eengoron 5
  • user_reassign script skips over objects

    user_reassign script skips over objects

    The user_reassign script doesn't update all the objects. It paginates through objects that match certain criteria (i.e. the old user), reassigns them to the new user, and then increases the offset to fetch the next batch. The problem is that when we reassign old objects, the results don't contain them anymore, and we end up skipping objects. We instead need to keep the offset at 0 and loop until no more objects are left (except for in the dry run).

    bug 
    opened by thomasst 5
  • Script to transfer leads (and nested contacts, notes, calls, etc.) from one organization to another

    Script to transfer leads (and nested contacts, notes, calls, etc.) from one organization to another

    Should take a search query as input and 2 API keys (from different orgs)

    Transfer all the leads matching the search query

    As well as all the nested objects on the leads (Contacts, Tasks, Opportunities), and the Calls & Note Activities

    Emails don't need to transfer since we'll sync them ourselves but all other lead data should transfer over.

    If lead statuses or opportunity statuses don't exist it should clearly print out that you need to set up those statuses within the destination organization first.

    enhancement 
    opened by anemitz 5
  • user reassign scripts doesn't appear to actually work

    user reassign scripts doesn't appear to actually work

    (closeio-api)--- lib/closeio-api ‹master» ./scripts/user_reassign.py --to-user-id user_wWDo6ETmCC9mSL3YQt0wdierIIKfZnR0LShSiVxEXBF --from-user-id user_fLuLxKSVFjJTePCe660YI2f2b5JpwoBjWQaPqWUjm5V -k <redacted> --all-tasks --all-opportunities -c
    

    Running the above command seems to modify the tasks and opportunities but if you run it again the see the same output / nothing actually changes.

    bug 
    opened by anemitz 4
  • Script to bulk update the country code of all addresses in leads matching a certain search query

    Script to bulk update the country code of all addresses in leads matching a certain search query

    bulk_update_address_countries.py

    inputs:

    • api key
    • old country code
    • new country code
    • leads search query (defaults to * sort:created if none) -- can be coded in script since it's hard to pass complicated queries (e.g. quotes) to cli args

    python cli script that takes these items as inputs. it should loop through each lead in the given search query, and if there are any addresses on that lead with the old country code then it should update the address with the new country code

    Would also like a read-only preview mode of what will happen and then a --confirmed flag

    @congocongo Can you work on this one after the other script?

    opened by philfreo 4
  • Add custom User Agent to every request, and expose the version in a readable way

    Add custom User Agent to every request, and expose the version in a readable way

    Closes #80

    This PR:

    • Changes the user agent sent on requests from python-requests/X to python closeio vY python-requests/X, where Y is the version of the closeio-api wrapper and X is the python-requests version
    • Adds a __version__ variable to __init__.py to keep track of the version.
    • Adds a helper to setup.py to read the __version__ variable from __init__.py to keep things consistent when installing the package.
    • Updates the version from 1.1 to 1.2

    I used the same structure as the Cleancat repo to do this.

    opened by eengoron 3
  • add pkg details to user agent

    add pkg details to user agent

    In response to https://github.com/closeio/closeio-api/issues/80.

    This modifies the default User-Agent to include package details: python closeio v{VERSION} {DEFAULT}

    Also breaks out the version constant into its' own version file. This helps to reduce the change of inconsistent versions, while also making it easy to add the seudo-standard __version__ attribute :)

    Testing

    Setup a simple server script to dump headers when making calls with the client. Then loaded the modified closeio_api and the output of the dump confirms this is working.

    ----- Request Start ----->
    
    /lead/
    Host: localhost:8000
    Connection: keep-alive
    Accept-Encoding: gzip, deflate
    Accept: */*
    User-Agent: python closeio v0.5 python-requests/2.11.1
    Content-Type: application/json
    X-TZ-Offset: -7
    Content-Length: 20
    Authorization: Basic abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx
    
    {"name": "New Lead"}
    <----- Request End -----
    
    127.0.0.1 - - [13/Mar/2017 17:25:57] "POST /lead/ HTTP/1.1" 200 -
    
    opened by JohnLZeller 3
  • Make the async version of the client easier to use

    Make the async version of the client easier to use

    This is an alternative to #78 fixing some of the async client's flaws instead of removing it altogether.

    Example usage:

    In [1]: from closeio_api import Client
    
    In [2]: api = Client('api_key', development=True, async=True)
    
    In [3]: results = api.map([
       ...:   api.post('lead', {'name': 'New Lead 1'}),
       ...:   api.post('lead', {'name': 'New Lead 2'}),
       ...:   api.post('lead', {'name': 'New Lead 3'}),
       ...:   api.post('lead', {'name': 'New Lead 4'})
       ...: ])
    
    In [4]: len(results)
    Out[4]: 4
    
    In [5]: results[0]['id']
    Out[5]: u'lead_RpzCIwspNlTUowYUvIUaNE1tp3ncVFk8x3Uw4sv96FO'
    

    I'm still conflicted whether such a deep integration of concurrency is good for this client library and its users or not. On the one hand, it makes concurrent requests easier to construct and send, and it handles API errors and retry logic properly (-ish). On the other hand, it makes the code more complex (and thus harder to read/understand), it forces a particular implementation of concurrency (green threads & gevent), and there are still some quirks with it (e.g. using the "debug" flag differs unintuitively between the sync and async client).

    @closeio/engineering what do you think?

    This PR introduces breaking changes and should be published along with a major version bump.

    opened by wojcikstefan 3
  • Update ratelimit behavior

    Update ratelimit behavior

    We want to update the behavior of our api and client to follow the draft RFC related to communicating rate limiting.

    See https://github.com/closeio/closeio/issues/28735

    opened by lmickh 0
  • APIError message should contain the response's status code

    APIError message should contain the response's status code

    So that it's obvious at a glance what kind of an error we experienced. Right now it's possible to get an enigmatic traceback:

    In [3]: api.get('me')
    Traceback (most recent call last)
    <ipython-input-3-842652bd220e> in <module>()
    ----> 1 api.get('me')
    
    /Users/wojcikstefan/Repos/closeio-api/closeio_api/__init__.py in get(self, endpoint, params, **kwargs)
        132         """
        133         kwargs.update({'params': params})
    --> 134         return self._dispatch('get', endpoint+'/', **kwargs)
        135
        136     def post(self, endpoint, data, **kwargs):
    
    /Users/wojcikstefan/Repos/closeio-api/closeio_api/__init__.py in _dispatch(self, method_name, endpoint, api_key, data, debug, **kwargs)
        108             raise ValidationError(response)
        109         else:
    --> 110             raise APIError(response)
        111
        112     def _get_rate_limit_sleep_time(self, response):
    
    APIError:
    
    opened by wojcikstefan 5
  • APIError is not very informative

    APIError is not very informative

    When a request fails because of an aborted connection or other unhandled exception the resulting APIError and the stack trace generated with it is not very informative. We should make the string representation of APIError include more detail.

    opened by lucasvo 1
Releases(v2.0)
  • v2.0(Apr 6, 2022)

    Changes in this release:

    • update 429 handling to use response headers rather than the json body
    • (Breaking Change) Drop python 2 support
    • Other minor or project-internal changes (update pytest, test on more recent python 3 versions, etc)
    Source code(tar.gz)
    Source code(zip)
  • v1.4(Dec 30, 2020)

  • 1.3(Jun 10, 2020)

  • v1.1(Feb 22, 2019)

  • v1.0(May 8, 2017)

    We're happy to publish a stable v1.0 of our Close.io API wrapper! The main highlights of this release are:

    • Significantly improved code quality, clarity, and documentation.
    • Automatic retrying of rate-limited requests.
    • Breaking change: Removal of the asynchronous code. If you relied on our async=True flag in the past, you'll have to refactor your application. You can choose to run multiple concurrent api.get/post/put/delete requests via threading, multiprocessing, concurrent.futures, gevent, etc., depending on your specific use case and environment.
    Source code(tar.gz)
    Source code(zip)
  • v0.5(Jan 24, 2017)

    • Fixed adding a trailing slash for GET requests.
    • Several fixes to the lead merge script.
    • Dropped flawed Python v3.x support.
    • Added script deleting tasks for inactive users.
    Source code(tar.gz)
    Source code(zip)
Owner
Close
The inside sales CRM of choice for SMBs. Join our eng team: http://jobs.close.com/
Close
Python3 script to dump employee information from XING API

XingDumper Python 3 script to dump company employees from XING API. Perfect OSINT tool ;-) The results contain firstname, lastname, position, gender,

LRVT 11 Dec 26, 2022
This an API wrapper library for the OpenSea API written in Python 3.

OpenSea NFT API Python 3 wrapper This an API wrapper library for the OpenSea API written in Python 3. The library provides a simplified interface to f

Attila Tóth 159 Dec 26, 2022
An implementation of webhook used to notify GitHub repository events to DingTalk.

GitHub to DingTask An implementation of webhook used to notify GitHub repository events to DingTalk.

Prodesire 5 Oct 02, 2022
A Matrix-Instagram DM puppeting bridge

mautrix-instagram A Matrix-Instagram DM puppeting bridge. Documentation All setup and usage instructions are located on docs.mau.fi. Some quick links:

89 Dec 14, 2022
This is Telegram Files Store Bot by @AbirHasan2005

PyroFilesStoreBot This is Telegram Parmanent Files Store Bot by @AbirHasan2005. Language: Python3 Library: Pyrogram Features: In PM Just Forward or Se

Abir Hasan 168 Dec 19, 2022
A Python wrapper for the QQ Channel API

A Python wrapper for the QQ Channel API

Fox_white 55 Dec 07, 2022
A multi-purpose Discord bot with simple moderation commands, reaction roles, reminders, and much more!

Nokari This is the rewrite of Nokari. There are still a lot of things to be done. I'm still working on the internal logic, so the bot basically has no

Norizon 13 Nov 17, 2022
A Telegram mirror bot which can be deployed using Heroku.

Slam Mirror Bot This is a telegram bot writen in python for mirroring files on the internet to our beloved Google Drive. Getting Google OAuth API cred

Hafitz Setya 1.2k Jan 01, 2023
A simpler way to make forms, surveys, and reaction input using discord.py.

discord-ext-forms An easier way to make forms and surveys in discord.py. This module is a very simple way to ask questions and create complete forms i

thrizzle 16 Nov 06, 2022
Python client for the Echo Nest API

Pyechonest Tap into The Echo Nest's Musical Brain for the best music search, information, recommendations and remix tools on the web. Pyechonest is an

The Echo Nest 655 Dec 29, 2022
ML-Test-Client

ML-Test-Client Introduction What is this? This Test Client App is to be used to crowd-test machine learning models with the goal of finding the best c

11 Jul 15, 2022
This is a repository for the Duke University Cloud Computing course project on Serveless Data Engineering Pipeline. For this project, I recreated the below pipeline.

AWS Data Engineering Pipeline This is a repository for the Duke University Cloud Computing course project on Serverless Data Engineering Pipeline. For

15 Jul 28, 2021
A simple Facebook Account generator, written in python (needs different Email so Accounts do not get banned)

FacebookAccountGenerator FAB is a Facebook-Account generating script, written in python Installation Use the package manager pip to install selenium p

MrOverload 7 Jan 05, 2023
:lock: Python 2.7/3.X client for HashiCorp Vault

hvac HashiCorp Vault API client for Python 3.x Tested against the latest release, HEAD ref, and 3 previous minor versions (counting back from the late

hvac 1k Dec 29, 2022
Projeto Informações Conta do Instagram - Instagram Account Information Project

VESTA-tools A collection of simple tools that proved to be needed for handling large periodic calculations with the VASP software package. distTotCalc

Thiago Souza 1 Dec 02, 2021
You can connect with Sanila Ranatunga using this bot😉😉

Sanila-Ranatunga-s-Assistant-Bot You can connect with Sanila Ranatunga using this bot 😉 😉 Reach me on Telegram Sanila's Assistant Bot What is Telegr

Sanila Ranatunga 5 Feb 01, 2022
Assistant made in python to control your spotify via voice

Spotify-Assistant Assistant made in python to control your spotify via voice Overview 🚀 PLAY, PAUSE, NEXT, PREVIOUS, VOLUME COMMANDS 📝 Toast notific

Mauri 6 Jan 18, 2022
My attempt at weaponizing Discord.

MayorbotC2 This is my Discord C2 bot. There are many like it, but this one is mine. MayorbotC2 is a project I absolutely forgot about until I was pilf

Joe Helle 19 May 16, 2022
A high level library for building Discord bots.

Qord A high level library for building Discord bots. 🚧 This library is currently in development. Questions that you are having What is this? This is

Izhar Ahmad 16 May 14, 2022
Gnosis-py includes a set of libraries to work with Ethereum and Gnosis projects

Gnosis-py Gnosis-py includes a set of libraries to work with Ethereum and Gnosis projects: EthereumClient, a wrapper over Web3.py Web3 client includin

Gnosis 93 Dec 23, 2022