Async-first Zoho Python SDK focused on DX and performance
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zoho-0.1.3.tar.gz.
File metadata
- Download URL: zoho-0.1.3.tar.gz
- Upload date:
- Size: 63.9 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a4ff3e0bc4849ded14b5e0220558ff40b704795a2ce02cca739f6ee1a459d98
|
|
| MD5 |
0744a3a437c959ab75ccaa7b0ce0e997
|
|
| BLAKE2b-256 |
5c9034198e33554ffdf917ac1fbc41504feada07bdcaf24272171e3340f7165a
|
File details
Details for the file zoho-0.1.3-py3-none-any.whl.
File metadata
- Download URL: zoho-0.1.3-py3-none-any.whl
- Upload date:
- Size: 100.3 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da3eb09fddda65282183ee9c2c0edf30183614812722ba89adbff3a3d84a68f1
|
|
| MD5 |
c2f418705212b760225b108c28c08150
|
|
| BLAKE2b-256 |
9117dc7f0f0ca2fee749bb68d0e87166a6189bf6f6f4e464f16cb3b9a78c6e2d
|