zenpy

A Python SDK for the Zendesk, Chat, Talk, and Help Center APIs with built-in caching, rate-limit handling, and lazy-loaded object relationships.

SDK
PyPI
v2.0.57
370stars
GNU GPLv3

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
62/100Good
Development Activity48
Maintenance32
Community88
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture78
Code Quality65
Innovation70
Learning Curve88

Zenpy is a Python client for the Zendesk, Chat, Talk, and Help Center REST APIs, built to keep integration code close to plain Python rather than raw JSON handling. It wraps Zendesk’s various endpoint families (tickets, users, organizations, macros, views, webhooks, and more) behind ORM-like objects, so a ticket.requester.name lookup or a ticket.tags.extend([...]) mutation quietly does the right API calls under the hood.

Under the surface it manages authentication (API token, OAuth, or client-credentials grants with automatic token refresh), Zendesk’s rate limiting (with a configurable ratelimit budget and an optional immediate-raise mode for schedulers like Celery), and object caching so repeated lookups by ID don’t re-hit the network. It supports both interactive/imperative use and Zendesk’s search/search-export APIs for bulk querying, making it a common backbone for support-engineering automation and internal Zendesk tooling.

What You Get

  • A unified Zenpy client exposing every Zendesk API family (tickets, users, organizations, macros, views, help center, talk, chat, webhooks) as attribute-style sub-APIs
  • Multiple authentication modes — email+API token, OAuth token, or an OAuth2 client-credentials grant with automatic token fetch and renewal
  • Built-in rate-limit handling with a configurable time budget, plus an optional raise_on_ratelimit mode for callers that want to reschedule work themselves
  • Object caching via ZenpyCacheManager, with per-object-type cache implementations you can inspect, resize, or swap at runtime
  • Cursor- and offset-based pagination handled transparently through ZendeskResultGenerator, including the dedicated search-export endpoint for large result sets

Common Use Cases

  • Automating ticket triage — searching, tagging, and updating tickets from a scheduled job or webhook handler
  • Syncing Zendesk users, organizations, and custom fields with an internal CRM or directory
  • Building Help Center content pipelines — creating categories, sections, and articles programmatically
  • Building internal admin tools that manage webhooks, macros, views, or ticket forms without hand-writing Zendesk’s REST payloads

Under The Hood

Architecture The Zenpy client in zenpy/__init__.py instantiates dozens of per-resource Api objects (defined in the 3,000+-line zenpy/lib/api.py), each wired with a shared config dict that threads the requests.Session, timeout, rate-limit settings, and cache manager through every sub-API. Each Api class composes a Request class (zenpy/lib/request.py) for building outgoing payloads, a ResponseHandler (zenpy/lib/response.py) for demultiplexing Zendesk’s varied JSON response shapes, and an object mapping (zenpy/lib/mapping.py’s ZendeskObjectMapping, ChatObjectMapping, HelpCentreObjectMapping, TalkObjectMapping) that turns raw JSON into typed Python objects. Pagination and lazy iteration live in zenpy/lib/generator.py’s ZendeskResultGenerator, caching in zenpy/lib/cache.py, and endpoint URL construction in zenpy/lib/endpoint.py’s EndpointFactory. This layered, composition-over-inheritance design means adding a new Zendesk resource is largely a matter of adding a Request/ResponseHandler/mapping trio rather than touching the client’s core mechanics — though the single large api.py file concentrates a lot of that composition in one place.

Tech Stack Zenpy targets Python 3.9+ and is built directly on requests for HTTP, with requests_oauth2client handling OAuth2 client-credentials token fetch and renewal, cachetools backing the pluggable object caches, and python-dateutil/pytz for Zendesk’s date handling; six remains a dependency as a holdover from earlier Python 2/3 dual support despite the project having dropped Python 2. Packaging is a plain setuptools/setup.py build (the repo carries only a stray, unused pyproject.toml.XXX placeholder). Tests run under pytest/nose with betamax/betamax-serializers replaying recorded HTTP fixtures instead of hitting live Zendesk, linting uses ruff pinned to a narrow, explicitly-scoped rule set (E4, E7, E9, F), and CI is a GitHub Actions matrix across Python 3.10–3.13 running make lint and make pytest.

Code Quality The project has an extensive tests/test_api/ suite (ticket CRUD, pagination, rate limiting, client-credentials auth, webhooks, proxies, engagements, and more), most of it driven by recorded betamax cassettes rather than live API calls, which keeps the suite deterministic and fast. Error handling goes through a small custom exception hierarchy in zenpy/lib/exception.py (ZenpyException, RateLimitError, RatelimitBudgetExceeded, RecordNotFoundException, APIException) rather than leaking raw requests exceptions, and naming follows a consistent *Api/*Request/*ResponseHandler convention across the library. The codebase predates widespread type-hint adoption in the Python ecosystem — none of the sampled core modules use type annotations, relying on docstrings instead — and some six/Python-2-compatibility code paths remain despite python_requires='>=3.9'.

API Design The headline ergonomic choice is that Zendesk resources feel like plain Python objects: ticket.requester.name triggers a lazy fetch instead of requiring the caller to manually resolve a requester_id, and objects can be dumped via .to_dict()/.to_json() without extra serialization code. Authentication is a single constructor call regardless of which of the four supported auth modes is used, and the client exposes every Zendesk API family as a same-shaped attribute (.tickets, .users, .webhooks, .help_center, …), so once you’ve learned one sub-API’s CRUD conventions the rest follow the same pattern. The tradeoff is that discovering the full surface requires reading api.py directly since there are no type stubs, and less common resource kinds are patched in ad hoc through the generic Api(config, object_type="...") constructor rather than a dedicated class.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search