Skip to content
Back to skills

Zoho Books

ASecurity

Zoho Books CLI for expenses, invoices, bills, bank transactions, contacts, projects, and payments — CRUD plus categorize/match, email, refunds, credits, and receipt attach/download. Triggers on Zoho Books operations or any Zoho Books record ID.

  • 8 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added May 26, 2026
developmentjavascriptgojavabashnodeexpressgitapi

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add madisonrickert/zoho-books-cli --skill zoho-books --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Zoho Books?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Zoho Books
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/madisonrickert-zoho-books/badge)](https://www.skillsdirectory.com/skills/madisonrickert-zoho-books)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: zoho-books
description: Zoho Books CLI for expenses, invoices, bills, bank transactions, contacts, projects, and payments — CRUD plus categorize/match, email, refunds, credits, and receipt attach/download. Triggers on Zoho Books operations or any Zoho Books record ID.
---

# Zoho Books CLI (`zb`) — agent usage

A thin command-line wrapper over the Zoho Books v3 REST API designed for agents:
- Outputs a single JSON object on stdout (`{"ok": true, "data": ...}`).
- Errors go to stderr with a stable `error.code` and a meaningful exit code.
- Preserves 19-digit Zoho IDs as strings end-to-end (the MCP loses precision in JS runtimes).
- Native multipart for local file uploads (the MCP can't carry binary).

This file is the **agent-user** reference. For contributing code to the CLI, see [`AGENTS.md`](https://github.com/madisonrickert/zoho-books-cli/blob/main/AGENTS.md). For human onboarding, see [`README.md`](https://github.com/madisonrickert/zoho-books-cli/blob/main/README.md).

Repo: <https://github.com/madisonrickert/zoho-books-cli>

## When to use this CLI vs. the Zoho MCP

| Task | Use |
| ---- | --- |
| Upload or download a local file (receipts, bill/invoice attachments, document PDFs) | **`zb`** |
| CRUD anywhere where the response IDs flow through a JS/Node runtime | **`zb`** (preserves 19-digit IDs as strings) |
| The two-step workflows below (apply payments to a bill, apply credits to an invoice, write off / cancel write-off, mark sent / void / draft) | **`zb`** has them as first-class verbs |
| MCP-only conveniences (full-text search, AI suggestions) | MCP |
| An endpoint neither wraps | `zb raw <METHOD> <path>` |

Both surfaces are valid; mix them freely. The IDs `zb` returns are safe to feed straight into MCP calls.

## Preconditions

```bash
zb --version       # confirms install
zb auth status     # → {"ok": true, "data": {"authenticated": true, ...}}
zb org current     # → {"ok": true, "data": {"org_id": "...", ...}}
```

If `auth status` shows `authenticated: false`, **stop** and tell the user to run `zb auth login --client-id <id> --client-secret <secret>`. You cannot complete the OAuth flow on their behalf.

If `org current` returns a null `org_id`, run `zb org list` and ask the user which organization to target, then `zb org use <id>`.

## IDs must be strings in `--body` JSON

Zoho Books IDs are 19 digits. JavaScript's `Number` type starts losing precision past ~16 digits (`2^53 − 1`). When you construct `--body` JSON, **quote IDs as strings**:

```bash
# Correct
zb expenses create --body '{"account_id":"9820000005670010000","amount":42.50}'

# Dangerous — a JS consumer of the response will silently corrupt the ID
zb expenses create --body '{"account_id":9820000005670010000,"amount":42.50}'
```

Numbers in JSON bodies (amounts, quantities, dates expressed as ints) are fine; the rule is specifically about ID-shaped fields.

## Thin-wrapper convention

Every wrapped command takes one or both of:

- `--query key=value` (repeatable) and / or `--params '<JSON>'` (single JSON object) for URL query params. Both merge into the final query dict; `--params` wins on conflict.
- `--body '<json>'` or `--body @path/to/file.json` for the request body. IDs in the body must be strings.

`--page` and `--per-page` are first-class on every list. Opt-in `--page-all` (with `--page-limit` and `--page-delay`) auto-paginates and emits **NDJSON** — one JSON line per page.

**There are no typed per-field flags — for filters or for body fields.** If a flag isn't in `<cmd> --help`, it doesn't exist. List filters go through `--query` / `--params` using Zoho's documented param names; bodies are built as JSON.

```bash
# Wrong — these flags don't exist; Typer will reject the call
zb expenses list --date-start 2022-08-10 --date-end 2022-08-15
zb expenses list --status unbilled

# Right — Zoho's documented query params, passed verbatim
zb expenses list --query from_date=2022-08-10 --query to_date=2022-08-15
zb expenses list --params '{"from_date":"2022-08-10","to_date":"2022-08-15"}'
zb expenses list --query status=unbilled --query vendor_id=98200000056700100
```

Filter param names come from Zoho's API reference for the underlying endpoint (`from_date`/`to_date`, `status`, `customer_id`, `vendor_id`, `search_text`, `filter_by`, ...) and vary by endpoint — bank-transactions statements use `from_date`/`to_date`, some other endpoints use `date_start`/`date_end`. **A mistyped or wrong-for-this-endpoint filter is silently ignored by Zoho** — the response looks fine but is unfiltered. If a filter doesn't seem to take, recheck the param name against Zoho's docs before assuming the data is correct.

## Output contract (parse this, not stdout text)

**Success** → stdout, exit 0:

```json
{"ok": true, "data": {...}}
```

**Error** → stderr, non-zero exit:

```json
{"ok": false, "error": {"code": "<code>", "message": "<human>", "details": {...}}}
```

Stable `error.code` + exit codes:

| `error.code` | Exit | Agent response |
| ------------ | ---- | -------------- |
| `auth_required` | 2 | Tell user to run `zb auth login`. Do not retry. |
| `auth_expired`  | 2 | Refresh token rejected; user must re-login. |
| `auth_failed`   | 2 | Login/refresh exchange failed; check client_id/secret. |
| `validation`    | 3 | Bad inputs (missing file, unsupported type, file >10 MB, malformed JSON body). Fix and retry. |
| `not_found`     | 4 | The record ID doesn't exist. Surface clearly; offer to list/search. |
| `api_error`     | 4 | Zoho returned 4xx. `details.zoho_code` carries Zoho's specific error code. |
| `rate_limited`  | 5 | `details.retry_after_s` says how long to wait. |
| `network`       | 6 | Transient. Retry once; if persistent, surface to user. |
| `unknown`       | 1 | Surface the message verbatim. |

## Output formatting flags

| Flag | Effect |
| ---- | ------ |
| `--format json` (default) | One-line JSON object. |
| `--format yaml` | YAML. |
| `--format table` | Rich table (humans). |
| `--format csv` | CSV (list responses only — falls back to JSON otherwise). |
| `--dry-run` | Print the request that *would* be sent (method, url, query, body, headers, files) as the success payload. **No network I/O, no token refresh.** Use to preview destructive calls. |

## Command tree (agent-relevant verbs)

> Authoritative tree: `zb --list-commands` returns the full command list as JSON.

### Auth & organization

```bash
zb auth login|status|refresh|logout
zb org list|use|current      # local config (target org)
zb org get <organization_id>     # API: GET /organizations/{id}
zb org update <organization_id> --body '{...}'
```

### Expenses (full surface + binary)

```bash
zb expenses list|get|create|update|update-by-custom-field|delete
zb expenses receipt upload|get|delete   # single receipt per expense; replaces existing
zb expenses attachments add|delete      # multiple supplementary files
zb expenses comments list
```

### Recurring expenses

```bash
zb recurring-expenses list|get|create|update|update-by-custom-field|delete
zb recurring-expenses stop|resume
zb recurring-expenses children          # child expenses
zb recurring-expenses history           # comments / activity
```

### Bank transactions + rules + statements

```bash
zb bank-transactions list|get|create|update|delete
zb bank-transactions match|matches|unmatch|exclude|restore|uncategorize
zb bank-transactions categorize generic|expense|vendor-payment|customer-payment|payment-refund|vendor-payment-refund|credit-note-refund|vendor-credit-refund
zb bank-transactions statements import|last-imported|delete

zb bank-rules list|get|create|update|delete   # /bankaccounts/rules; list requires account_id query param
```

### Bills (full)

```bash
zb bills list|get|create|update|update-by-custom-field|delete
zb bills mark-void|mark-open
zb bills email                          # POST /bills/{id}/email
zb bills payments list|apply|delete     # `delete` unapplies a payment row
zb bills comments list
zb bills attachments add|get|delete     # `get` writes the file to --output
```

### Invoices (full)

```bash
zb invoices list|get|create|update|update-by-custom-field|delete
zb invoices mark-sent|mark-void|mark-draft
zb invoices write-off|cancel-write-off
zb invoices email                       # ad-hoc email
zb invoices reminders send              # uses Zoho's payment-reminder template (placeholders, dunning rules)
zb invoices payments list               # read-only; record payments via `customer-payments create`
zb invoices credits list|apply|delete   # apply existing credit-notes / customer-payments
zb invoices comments list|add|delete
zb invoices documents get|download|delete   # `download` writes bytes via responseformat=pdf|html
zb invoices attachments add|get|delete
zb invoices templates list|apply
```

### Recurring invoices

```bash
zb recurring-invoices list|get|create|update|update-by-custom-field|delete
zb recurring-invoices stop|resume
zb recurring-invoices history
zb recurring-invoices templates apply <id> <template_id>
```

### Customer payments

```bash
zb customer-payments list|get|create|update|update-by-custom-field|delete
zb customer-payments refunds list|get|create|update|delete
```

### Projects (full)

```bash
zb projects list|get|create|update|update-by-custom-field|delete
zb projects mark-active|mark-inactive|clone
zb projects invoices                    # invoices billed against this project
zb projects users list|get|add|invite|update|delete
zb projects tasks list|get|add|update|delete
zb projects comments list|add|delete
```

### Contacts (full)

```bash
zb contacts list|search|get|create|update|update-by-custom-field|delete
zb contacts mark-active|mark-inactive
zb contacts comments
zb contacts addresses list|add|update|delete <contact_id> [<address_id>] ...
zb contacts persons list|get|create|update|delete|mark-primary
```

### Chart of accounts

```bash
zb chart-of-accounts list|get|create|update|delete
zb chart-of-accounts mark-active|mark-inactive
zb chart-of-accounts transactions list|delete
```

### Escape hatch

```bash
zb raw <GET|POST|PUT|DELETE> <path> [--query k=v] [--body '<json>'|@file.json] [--file field=path]
```

### File constraints (validated client-side)

- Types: `.pdf`, `.jpg`, `.jpeg`, `.png`, `.gif`
- Size: ≤ 10 MB per file

## Multi-file upload semantics

`attachments add <id> file1 file2 ...` does **not** abort on the first failure. Iterate `data.results[]`:

```json
{
  "ok": true,
  "data": {
    "expense_id": "...",
    "results": [
      {"file": "a.pdf", "ok": true,  "response": {...}},
      {"file": "b.exe", "ok": false, "error": {"code": "validation", ...}}
    ]
  }
}
```

`receipt upload` (one file per expense) returns success/failure as the top-level envelope and **replaces** any existing receipt.

## Idempotency notes

- `receipt upload` replaces.
- `attachments add` appends — calling twice with the same file creates two attachments.
- `attachments delete` and `receipt delete` are idempotent; deleting an already-deleted item returns `not_found`.
- Status verbs (`mark-active`, `mark-inactive`, `mark-sent`, `mark-void`, `stop`, `resume`, etc.) are server-validated for eligibility — you can call them on a record already in the target state without harm, but the response message will reflect Zoho's view.
- `bills payments delete` and `invoices credits delete` only **unapply** an application row; the underlying payment / credit-note record is untouched and re-applicable elsewhere.

## Typical agent flows

**Attach a receipt to an expense:**
1. `zb auth status` (verify) → `zb org current` (verify org_id).
2. `zb expenses receipt upload <expense_id> /path/to/file.pdf`.
3. Parse response: on `ok:true`, confirm `data.uploaded` (filename) and `data.size_bytes`. On `error.code=not_found`, offer to list recent expenses to find the right ID.

**Email an invoice:**
1. `zb invoices email <invoice_id>` — empty body sends Zoho's default to the saved-on-record recipients.
2. To override recipients/subject: `--body '{"to_mail_ids":["a@b.com"],"subject":"...","body":"<p>…</p>"}'`.
3. To attach the PDF: `--query send_attachment=true`.
4. For dunning / overdue reminders that pick up the org's payment-reminder template, use `zb invoices reminders send` instead.

**Apply credits to an invoice:**
1. Discover applicable credits via `zb customer-payments list` and `zb raw GET /creditnotes`.
2. `zb invoices credits apply <invoice_id> --body '{"apply_creditnotes":[{"creditnote_id":"...","amount_applied":100}]}'`.
3. Inspect the result via `zb invoices credits list <invoice_id>`.

**Categorize an uncategorized bank transaction as an expense:**
1. `zb bank-transactions list --query status=uncategorized` (or `--query account_id=...`).
2. `zb bank-transactions categorize expense <txn_id> --body '{"account_id":"...","amount":...,"date":"..."}'`.
3. Confirm via `zb bank-transactions get <txn_id>`.

## Discoverability

```bash
zb --list-commands         # full tree as JSON
zb <group> --help          # per-group help
zb <group> <cmd> --help    # per-command help (shows --body / --query expectations)
```

Use `--list-commands` after a CLI upgrade to discover new verbs without re-reading this skill.

## Full developer / contributor reference

See [`AGENTS.md`](https://github.com/madisonrickert/zoho-books-cli/blob/main/AGENTS.md) for architecture, contribution patterns, and the test contract (relevant only if you're modifying the CLI itself).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.