zoho
Async-first Python SDK for Zoho, designed for developer experience and performance.
Highlights
- Async-first transport built on
httpx - Explicit credential-first initialization (
from_credentials) - Strong typing with
pydantic/pydantic-settings - Pluggable token stores (memory, SQLite, Redis)
- Structlog-powered logging (
prettyorjson) - Multi-account connection manager (
client.connections) - Product clients:
- CRM (
records,modules,org,users,dynamic) - Creator (
meta,data,publish,dynamic) - Projects V3 (
portals,projects,tasks) - People (
forms,employees,files) - Sheet (
workbooks,worksheets,tabular) - WorkDrive (
files,folders,search,changes,admin) - Cliq (
users,chats,channels,messages,threads) - Analytics (
metadata,data,bulk) - Writer (
documents,folders,merge) - Mail (
accounts,folders,messages,threads)
- CRM (
- Ingestion iterators for connector workloads (
zoho.ingestion) - Codegen tooling + golden tests for spec drift
Installation
uv add zoho
Optional extras:
uv add "zoho[redis]" # Redis token store
uv add "zoho[orjson]" # Faster JSON usage patterns
Quick Start (Explicit Credentials)
from zoho import Zoho
async def main() -> None:
async with Zoho.from_credentials(
client_id="your_client_id",
client_secret="your_client_secret",
refresh_token="your_refresh_token",
dc="US",
environment="production",
) as client:
lead = await client.crm.records.get(module="Leads", record_id="123456789")
print(lead.id)
Client Lifecycle: Context Manager vs Singleton
Both patterns are supported.
Use async with for one-shot scripts/jobs:
async with Zoho.from_credentials(
client_id="...",
client_secret="...",
refresh_token="...",
) as client:
org = await client.crm.org.get()
Use a long-lived singleton for web apps/workers and close on shutdown:
zoho_client = Zoho.from_credentials(
client_id="...",
client_secret="...",
refresh_token="...",
)
project_rows = await zoho_client.projects.projects.list(portal_id="12345678")
# shutdown hook
await zoho_client.close()
After close(), zoho_client.closed is True and that instance must not be reused.
Multi-Account Connections
from zoho import Zoho, ZohoConnectionProfile
client = Zoho.from_credentials(
client_id="primary_client_id",
client_secret="primary_client_secret",
refresh_token="primary_refresh_token",
)
client.register_connection(
ZohoConnectionProfile(
name="tenant_b",
client_id="tenant_b_client_id",
client_secret="tenant_b_client_secret",
refresh_token="tenant_b_refresh_token",
dc="EU",
token_store_backend="sqlite",
)
)
tenant_b = client.for_connection("tenant_b")
forms = await tenant_b.people.forms.list_forms()
print(forms.result_rows)
Product Usage Examples
People
records = await client.people.forms.list_records(
form_link_name="employee",
limit=200,
)
print(records.result_rows)
Sheet
rows = await client.sheet.tabular.fetch_worksheet_records(
workbook_id="workbook_123",
worksheet_name="Data",
limit=500,
)
print(rows.records)
WorkDrive
changes = await client.workdrive.changes.list_recent(
folder_id="folder_123",
limit=200,
)
print(changes.resources)
Cliq
channels = await client.cliq.channels.list(limit=50)
print(channels.result_rows)
Analytics
orgs = await client.analytics.metadata.list_organizations()
print(orgs.result_rows)
Writer
documents = await client.writer.documents.list(limit=20)
print(documents.result_rows)
accounts = await client.mail.accounts.list()
print(accounts.result_rows)
CRM Dynamic Discovery
if await client.crm.dynamic.has_module("Leads"):
leads = client.crm.dynamic.Leads
rows = await leads.list(page=1, per_page=200)
print(rows.data)
Creator Dynamic Discovery
apps = await client.creator.dynamic.list_applications()
inventory = await client.creator.dynamic.get_application_client("owner.inventory-app")
forms = await inventory.meta.get_forms()
print(forms.data)
Precompile dynamic metadata for faster cold starts:
await client.crm.dynamic.precompile_modules()
await client.creator.dynamic.precompile_applications()
Ingestion Helpers (pipeshub-ai-friendly)
from zoho.ingestion import iter_people_form_documents
async for batch in iter_people_form_documents(
client,
form_link_name="employee",
connection_name="tenant_b",
page_size=200,
):
for doc in batch.documents:
print(doc.id, doc.title)
print(batch.checkpoint)
from zoho.ingestion import iter_cliq_chat_documents
async for batch in iter_cliq_chat_documents(
client,
include_messages=True,
page_size=200,
):
for doc in batch.documents:
print(doc.source, doc.id)
from zoho.ingestion import iter_analytics_view_documents
async for batch in iter_analytics_view_documents(
client,
workspace_id="workspace_123",
view_id="view_123",
strategy="bulk", # or "direct"
headers={"ZANALYTICS-ORGID": "123456789"},
):
print(batch.checkpoint, len(batch.documents))
Additional iterators:
iter_crm_module_documents(...)iter_crm_documents(...)iter_cliq_channel_documents(...)iter_cliq_chat_documents(...)iter_cliq_thread_documents(...)iter_analytics_workspace_documents(...)iter_analytics_view_documents(...)iter_sheet_worksheet_documents(...)iter_workdrive_recent_documents(...)iter_mail_message_documents(...)iter_writer_document_documents(...)
Getting OAuth Credentials
If you still need OAuth credentials, follow:
docs/auth-credentials.mddocs/scopes.md
At a high level:
- Create a client in Zoho API Console.
- Generate grant code(s) with required product scopes.
- Exchange grant code for access/refresh tokens.
- Use matching
dcand accounts domain.
Auth Helper CLI
Use the helper command for token exchange and self-client payload generation:
export ZOHO_CREDENTIALS_FILE=refs/notes/zoho-live.env
uv run zoho-auth exchange-token --grant-code "<grant-code>"
uv run zoho-auth grant-code \
--self-client-id "1000..." \
--scopes "ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.users.ALL,ZohoCRM.org.ALL"
uv run zoho-auth scope-builder \
--product CRM \
--product WorkDrive \
--product Mail \
--product Writer \
--access read \
--format env
See docs/auth-cli.md for execute mode and header/cookie options.
Environment-Based Setup (Convenience)
export ZOHO_CLIENT_ID="..."
export ZOHO_CLIENT_SECRET="..."
export ZOHO_REFRESH_TOKEN="..."
export ZOHO_DC="US"
export ZOHO_ENVIRONMENT="production"
from zoho import Zoho
async with Zoho.from_env() as client:
org = await client.crm.org.get()
print(org)
Live Credential Validation (Admin)
Use the read-only validator before production rollout:
export ZOHO_CREDENTIALS_FILE=refs/notes/zoho-live.env
uv sync --group dev
uv run python tools/admin_validate_live.py
The script only runs read-oriented product checks and prints non-sensitive summaries
(counts/status only). See docs/admin-live-validation.md for required/optional vars.
Security Scan (Pre-Public / Pre-Release)
Run the high-confidence scanner against tracked files and full git history:
uv run python tools/security_scan.py --mode all --report .security/secrets-report.json
If findings are detected, rotate/revoke affected credentials and clean files/history
before publishing. See SECURITY.md for the response process.
Development
uv sync --group dev
uv run ruff format .
uv run ruff check .
uv run mypy
uv run pytest
uv run mkdocs build --strict
Codegen Workflows
CRM summary
uv run python tools/codegen/main.py \
--json-details tests/fixtures/json_details_minimal.json \
--openapi tests/fixtures/openapi_minimal.json \
--output /tmp/zoho_ir_summary.json
Creator summary
uv run python tools/codegen/creator_summary.py \
--openapi tests/fixtures/creator_openapi_minimal.json \
--output /tmp/creator_summary.json
Projects extraction
uv run python tools/codegen/projects_extract.py \
--html tests/fixtures/projects/api_docs_sample.html \
--output /tmp/projects_mvp.json
Curated product specs summary (People/Sheet/WorkDrive)
uv run python tools/codegen/curated_summary.py \
--spec tools/specs/people_v1_curated.json \
--spec tools/specs/sheet_v2_curated.json \
--spec tools/specs/workdrive_v1_curated.json \
--output /tmp/curated_summary.json
Repository Docs
- Product docs:
docs/ - Use-case playbooks:
docs/use-cases/ - API research notes:
refs/apis/ - Design specs:
refs/docs/specs/ - Contributor guide:
AGENTS.md
Metadata
Release files for zoho 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zoho-0.1.3.tar.gz | 63.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zoho-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 164.2 kB
Release files / zoho-0.1.3.tar.gz
| Download URL | zoho-0.1.3.tar.gz |
|---|---|
| Size | 63.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1a4ff3e0bc4849ded14b5e0220558ff40b704795a2ce02cca739f6ee1a459d98
|
|
BLAKE2b-256 checksum How to use checksums |
5c9034198e33554ffdf917ac1fbc41504feada07bdcaf24272171e3340f7165a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / zoho-0.1.3-py3-none-any.whl
| Download URL | zoho-0.1.3-py3-none-any.whl |
|---|---|
| Size | 100.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
da3eb09fddda65282183ee9c2c0edf30183614812722ba89adbff3a3d84a68f1
|
|
BLAKE2b-256 checksum How to use checksums |
9117dc7f0f0ca2fee749bb68d0e87166a6189bf6f6f4e464f16cb3b9a78c6e2d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|