Skip to main content

Modern, async-ready Python client for Zoho APIs (Books, Inventory, and more)

Project description

ZohoPy

CI PyPI Python License: MIT

Modern, async-ready Python client for Zoho APIs.

Full coverage of Zoho Books (API v3) with both sync and async interfaces. Designed as a standalone, modular library — easy to maintain, extend, and use as a Git submodule.

Features

  • Sync + AsyncSyncZohoClient or AsyncZohoClient
  • Complete Zoho Books coverage — 30+ resource types with full CRUD + actions
  • Auto data-center detection — discovered from api_domain during OAuth
  • Auto-auth — OAuth2 tokens refresh transparently
  • Auto-pagination — iterate all pages with list_all()
  • Retry & back-off — handles 429 rate limits and 5xx server errors
  • Structured logging — JSON/console output via structlog
  • Typed exceptions — 10+ specific exception classes for every Zoho error pattern
  • Docker ready — Dockerfile + docker-compose with proper env/secrets
  • Type-safe — full type hints, py.typed, mypy strict
  • Zero config — reads .env via pydantic-settings
  • Interactive setuppython -m zohopy wizard with browser OAuth flow

Products

Product Status API Version
Zoho Books ✅ Full coverage v3
Zoho Inventory 🔜 Coming soon v1
Zoho CRM 🔜 Coming soon v7
Zoho Payroll 🔜 Planned
Zoho People 🔜 Planned

Installation

pip install zohopy

Setup

Option A: Interactive wizard (recommended)

python -m zohopy

Supports both Self Client (paste code) and Browser redirect (automatic) OAuth flows. Generates .env for you.

Option B: Manual

  1. Go to api-console.zoho.com → create a Self Client
  2. Generate a grant code with scope ZohoBooks.fullaccess.all
  3. Exchange it:
from zohopy.setup import exchange_grant_token

result = exchange_grant_token(
    client_id="YOUR_CLIENT_ID",
    client_secret="YOUR_CLIENT_SECRET",
    grant_token="THE_GRANT_CODE",
)
# result["refresh_token"] — save this (permanent)
# result["api_domain"] — auto-detects your data center
  1. Create .env:
ZOHO_CLIENT_ID=...
ZOHO_CLIENT_SECRET=...
ZOHO_REFRESH_TOKEN=...
ZOHO_ORGANIZATION_ID=...
ZOHO_API_DOMAIN=https://www.zohoapis.com

Option C: Programmatic (for submodule integration)

from zohopy import ZohoConfig, SyncZohoClient

config = ZohoConfig(
    client_id="...",
    client_secret="...",
    refresh_token="...",
    organization_id="...",
    api_domain="https://www.zohoapis.com",
    _env_file=None,  # skip .env loading
)

Quick Start

from zohopy import ZohoConfig, SyncZohoClient
from zohopy.products.books import ZohoBooks

with SyncZohoClient(ZohoConfig()) as client:
    books = ZohoBooks(client)

    # Contacts
    contacts = books.contacts.list()
    customer = books.contacts.create({"contact_name": "Acme Corp", "contact_type": "customer"})

    # Invoices
    invoice = books.invoices.create({
        "customer_id": "CUSTOMER_ID",
        "line_items": [{"item_id": "ITEM_ID", "quantity": 1, "rate": 100}],
    })
    books.invoices.mark_sent(invoice["invoice"]["invoice_id"])

    # Auto-paginate
    for page in books.items.list_all():
        for item in page.get("items", []):
            print(item["name"])

Async

import asyncio
from zohopy import ZohoConfig, AsyncZohoClient
from zohopy.products.books import AsyncZohoBooks

async def main():
    async with AsyncZohoClient(ZohoConfig()) as client:
        books = AsyncZohoBooks(client)
        contacts = await books.contacts.list()
        async for page in books.items.list_all():
            print(len(page.get("items", [])))

asyncio.run(main())

Error Handling

ZohoPy maps every Zoho API error to a typed exception:

from zohopy import (
    ZohoNotFoundError,
    ZohoRateLimitError,
    ZohoValidationError,
    ZohoDuplicateError,
    ZohoFeatureNotEnabledError,
    ZohoInvalidFieldError,
)

try:
    books.invoices.get("nonexistent")
except ZohoNotFoundError:
    print("Invoice not found")
except ZohoRateLimitError as e:
    print(f"Rate limited — retry after {e.retry_after}s")
except ZohoDuplicateError:
    print("Already exists")
except ZohoInvalidFieldError as e:
    print(f"Bad field: {e.field_name}")
except ZohoFeatureNotEnabledError:
    print("Enable the feature in Zoho Settings")
except ZohoValidationError as e:
    print(f"Validation: {e.details}")

Exception Hierarchy

ZohoError
├── ZohoTokenRefreshError          — OAuth token refresh failed
└── ZohoAPIError                   — HTTP error from Zoho
    ├── ZohoAuthenticationError    — 401: bad/expired token
    ├── ZohoForbiddenError         — 403: insufficient scopes
    ├── ZohoNotFoundError          — 404: resource not found
    ├── ZohoRateLimitError         — 429: rate limit (.retry_after)
    ├── ZohoServerError            — 5xx: Zoho server error
    └── ZohoValidationError        — 400: bad request
        ├── ZohoInvalidFieldError      — bad field value (.field_name)
        ├── ZohoDuplicateError         — record already exists
        ├── ZohoEmptyBodyError         — missing request body
        ├── ZohoBusinessRuleError      — business rule violation
        ├── ZohoFeatureNotEnabledError — feature not enabled in org
        └── ZohoResourceDependentError — has transactions/dependents

Zoho Error Code Reference

Code Exception When
2 ZohoInvalidFieldError Invalid value for a field
4 ZohoDuplicateError / ZohoInvalidFieldError Already exists or invalid format
5 ZohoNotFoundError Invalid endpoint URL
6 ZohoInvalidFieldError Invalid resource reference
11 ZohoEmptyBodyError Empty/malformed request body
14 ZohoAuthenticationError Invalid auth token
36 ZohoResourceDependentError Cannot delete, has dependents
57 ZohoAuthenticationError Not authorized
1002 ZohoNotFoundError Resource deleted/inaccessible
1004 ZohoResourceDependentError Has open transactions
4016 ZohoBusinessRuleError No line items selected
110817 ZohoFeatureNotEnabledError Sales Tax not enabled

Zoho Books API Coverage

Sales Cycle

Resource CRUD Actions
Contacts active/inactive, portal, payment reminders, addresses
Estimates sent/accepted/declined, approve, email
Sales Orders open/void, approve, email
Invoices sent/void/draft, approve, email, write-off, credits, payment link
Recurring Invoices stop/resume
Sales Receipts email
Credit Notes void/draft/open, approve, apply to invoice, refund
Customer Payments refund
Retainer Invoices sent/void/draft, approve, email

Purchase Cycle

Resource CRUD Actions
Purchase Orders open/billed/cancelled, approve, email
Bills void/open, approve, credits
Recurring Bills stop/resume
Vendor Payments refund, email
Expenses receipts
Recurring Expenses stop/resume

Items & Inventory

Resource CRUD Actions
Items active/inactive

Banking & Accounting

Resource CRUD Actions
Bank Accounts activate/deactivate
Bank Transactions match/unmatch, categorize, exclude/restore
Bank Rules
Chart of Accounts active/inactive
Journals publish
Base Currency Adjustments

Projects & Time

Resource CRUD Actions
Projects activate/inactivate, clone
Tasks open/ongoing/completed
Time Entries start/stop timer

Fixed Assets

Resource CRUD Actions
Fixed Assets active/cancel/draft, write-off, sell

Settings & Configuration

Resource Operations
Organizations CRUD
Taxes CRUD + tax groups
Currencies CRUD + exchange rates
Users CRUD + current user, invite, active/inactive
Preferences get/update
Templates list
Opening Balances CRUD
Custom Fields list by entity, CRUD, reorder
Custom Views CRUD
Reporting Tags CRUD + active/inactive
Workflows list

Logging

from zohopy import configure_logging

# Development (colored console)
configure_logging(level="DEBUG", log_format="console")

# Production / Docker (structured JSON)
configure_logging(level="INFO", log_format="json")

# Or via env vars:
#   ZOHOPY_LOG_LEVEL=INFO
#   ZOHOPY_LOG_FORMAT=json

Docker

# Build
docker compose build

# Interactive OAuth setup
docker compose run --rm zohopy-setup

# Run a script
docker compose run --rm zohopy-script

Configuration

Env Variable Required Default Description
ZOHO_CLIENT_ID OAuth client ID
ZOHO_CLIENT_SECRET OAuth client secret
ZOHO_REFRESH_TOKEN Long-lived refresh token
ZOHO_ORGANIZATION_ID Zoho org ID
ZOHO_API_DOMAIN Auto-detects data center
ZOHO_DATA_CENTER us Override: us/eu/in/au/jp/ca/cn/sa
ZOHOPY_LOG_LEVEL INFO DEBUG/INFO/WARNING/ERROR
ZOHOPY_LOG_FORMAT console console/json

Development

git clone https://github.com/talas9/zohopy.git
cd zohopy
git checkout dev
pip install -e ".[dev]"

# Tests
pytest tests/unit/                         # unit tests (mocked)
pytest tests/integration/ -m integration   # live API tests (needs .env)

# Quality
ruff check src/ tests/
ruff format src/ tests/
mypy src/

Branch Strategy

Branch Purpose
main Stable releases only. Protected — requires PR with passing CI.
dev Active development. All work merges here first.
v* tags Trigger PyPI release from main only.

Release Process

  1. Develop on dev branch
  2. Open PR from devmain
  3. CI must pass (lint + type-check + tests on Python 3.10–3.13)
  4. Merge to main
  5. Tag: git tag v0.2.0 && git push origin v0.2.0
  6. GitHub Actions builds + publishes to PyPI + creates GitHub Release

Contributing

  1. Fork → clone → git checkout -b feat/my-feature from dev
  2. Make changes with tests
  3. ruff check && mypy src/ && pytest
  4. Open PR to dev

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zohopy-0.1.0.tar.gz (53.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zohopy-0.1.0-py3-none-any.whl (65.1 kB view details)

Uploaded Python 3

File details

Details for the file zohopy-0.1.0.tar.gz.

File metadata

  • Download URL: zohopy-0.1.0.tar.gz
  • Upload date:
  • Size: 53.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zohopy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0e9ace08f63c10953d204f45f670abf4b0f0c1aff7e7163552ae96cfa5759e61
MD5 26cac7bb774f586eace37c9bca348168
BLAKE2b-256 c0f5215135beaca478e3987ff1266500c9bcfee32518dfb9148de9316b7ee9fc

See more details on using hashes here.

Provenance

The following attestation bundles were made for zohopy-0.1.0.tar.gz:

Publisher: release.yml on talas9/zohopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zohopy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: zohopy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 65.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zohopy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5a4969895b7e2d38bf15ea9b4b80ba981b19624503c04c7d04ea143c6f19621b
MD5 a4a19ad4c385c646332329ed673d9eba
BLAKE2b-256 c04e16c1a3dcf4ab74888b2729773aba98f9903378bdf115f302075c938eb106

See more details on using hashes here.

Provenance

The following attestation bundles were made for zohopy-0.1.0-py3-none-any.whl:

Publisher: release.yml on talas9/zohopy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page