### Zoho Invoice — the org id is a header, the Books command names are not the same, and there is no ledger underneath

Zoho Invoice is the **receivables-only** sibling of Books: same v3 finance API, same `{"code":0,…}` envelope, same `page_context` paging, same "the write body *is* the record" shape — but roughly a third of the surface (268 commands in 21 groups, against Books' 903). Almost every failure is one of three things: the org header is missing, a Books command name was carried over that does not exist here, or the task needs a ledger feature Invoice simply does not have.

Money rules — void vs delete vs credit note vs write-off, why a sent document is immutable, why number sequences must not gap, and dry-running a financial write — live in the cross-cutting accounting skill (`zone llm accounting`). Read that before the first write. This guide covers only what is specific to Invoice.

#### The organization id rides in a header

```bash
zone invoice org list --toon                   # organization_id, name, currency — also the auth probe
zone ctx invoice organization_id=<id>          # injected as X-com-zoho-invoice-organizationid
```

The ctx key is `organization_id`, but zone injects it as the **header** `X-com-zoho-invoice-organizationid`, not as a query parameter. That matters: typed commands have no `--query` flag, so there is no per-call override the way Books allows. To read another org once, drop to the proxy and set the header yourself:

```bash
zone api invoice GET /invoices --header X-com-zoho-invoice-organizationid=<otherOrgId>
```

Take the id from `zone invoice org list` rather than from another finance service's org list — an org can exist without Invoice provisioned on it, and the failure then looks like an empty result rather than an error.

#### Envelope and paging

Books-shaped and unchanged: `{"code":0,"message":"success", …}`, the **plural** resource key on lists (`invoices`, `contacts`, `estimates`, `customerpayments`), the **singular** on a single record. Lists carry `page_context`; walk `--page` / `--per-page` until `has_more_page` is false. The specs document a `per_page` default of 200 on every paged endpoint, so treat 200 as the working page size.

#### Command names that are *not* the Books ones

This is where a Books-trained agent breaks. Left is the habit; right is what exists here.

| Reflex from Books | Zoho Invoice |
|---|---|
| `zone books invoice sent <id>` | `zone invoice invoice mark-sent <id>` |
| `zone books invoice writeoff <id>` | `zone invoice invoice write-off <id>` — and it is reversible: `cancel-write-off` |
| `zone books invoice pdf <id>` | `zone invoice invoice export-multiple-as-pdf --invoice-ids <id> --out f.pdf` (no single-invoice PDF verb) |
| `zone books invoice create --send` | no `--send` here: `invoice create`, then `invoice mark-sent`, then `invoice email` |
| `zone books tax list` | `zone invoice setting list-all-taxes` |
| `zone books currency list` | `zone invoice setting list-all-currencies` |
| `zone books contactperson list` | `zone invoice contact list-all-persons <contactId>`, `contact create-person` |
| `zone books payment get <id>` | `zone invoice payment get-customer <paymentId>` |

The `payment` group is the sharpest of these. It is `/customerpayments` underneath, and the leaves are named `get-customer`, `update-customer`, `delete-customer`, `refund-excess-amount-customer` — the "customer" suffix describes the *endpoint family*, not the argument. Every one of them takes a **payment** id.

Auto-numbering is inconsistent too: `creditnote create`, `estimate update` and `retainerinvoice create` accept `--ignore-auto-number-generation`, but `invoice create` does not. Let Invoice number the document.

#### What Invoice does not have

None of these groups exist; asking for them fails as an unknown command, not as a permission error.

- **No purchase side**: no bills, vendor payments, vendor credits, purchase orders, recurring bills. Invoice bills customers; it does not track what you owe.
- **No general ledger**: no chart of accounts, no journal entries, no recurring journals, no opening balances.
- **No banking**: no bank feeds, bank rules, registers or reconciliation. You can *record* a payment; you cannot match it to a bank line.
- **No order or stock documents**: no sales orders, delivery challans, sales receipts, inventory adjustments, fixed assets.
- **No period control**: no accounting period, no transaction lock.
- **No e-invoicing, reporting tags, custom-field discovery, custom modules, or `pull` snapshot.**

Two consequences to plan around. First, the accounting skill's correction of last resort — reversing a journal entry — has no equivalent here; the only reversals are `invoice void`, a credit note, and `invoice write-off`. Second, because there is no transaction lock to query, **nothing stops a back-dated write landing in a period a bookkeeper already closed and reported.** Confirm the date with a human instead of trusting the API to refuse.

Expenses and time tracking do exist, in unobvious places: `zone invoice expense list`, categories under `zone invoice expensecategory list-all-expense-categories`, and time entries under the project group — `zone invoice project list-all-time-entries`, `project log-time-entry`, `project start-timer <timeEntryId>`.

#### Writing

A create/update body is the record itself — no `{"data":[…]}` wrapper, no batching, one call per record. `--data` takes inline JSON, `@file.json`, or `-` for stdin.

```bash
zone invoice contact create --data '{"contact_name":"Acme LLC","currency_code":"AED","contact_persons":[{"first_name":"Jane","email":"jane@acme.ae","is_primary_contact":true}]}'

zone invoice invoice create --data '{"customer_id":"<contact_id>","date":"2026-09-01","due_date":"2026-09-30","line_items":[{"item_id":"<item_id>","quantity":10,"rate":300},{"name":"Travel","quantity":1,"rate":450}]}'

zone invoice invoice mark-sent <invoiceId>
zone invoice invoice email <invoiceId> --data '{"to_mail_ids":["ap@acme.ae"],"subject":"Invoice INV-000044"}'

zone invoice payment create --data '{"customer_id":"<contact_id>","payment_mode":"Bank Transfer","amount":5250,"date":"2026-09-02","invoices":[{"invoice_id":"<invoiceId>","amount_applied":5250}]}'
```

Ids are 19-digit **strings** — never parse one to a number. Dates are `yyyy-MM-dd`. Money is a plain number in the record's `currency_code`. Send line items and tax ids and let Zoho compute the totals; a total you calculated that differs by a cent is a document that does not match the books.

`status` is never written as a field — it moves through the status commands (`mark-sent`, `void`, `mark-voided-as-draft`, `write-off`, `cancel-write-off`), and the same pattern holds for estimates (`mark-as-sent`, `mark-as-accepted`, `mark-as-declined`) and retainer invoices.

Reversal, in the order the accounting skill prescribes:

```bash
zone invoice invoice get <id> --json                  # read the status BEFORE deciding
zone invoice invoice void <id> --dry-run --json       # show the write, send nothing
zone invoice creditnote create --invoice-id <id> --data '{…}'
zone invoice creditnote apply-credit-invoice <creditnoteId> --data '{…}'
```

`invoice delete` is for drafts only — on an issued document it leaves a gap in a legally sequential number series. Void keeps the number.

#### Errors → what to do

| What you see | Meaning | Do |
|---|---|---|
| results from the wrong company, or an empty list you did not expect | the org header is unset or points at another org | `zone invoice org list`, then `zone ctx invoice organization_id=<id>` |
| "no such command" for `tax`, `currency`, `coa`, `journal`, `bill`, `salesorder` | Books vocabulary, or a feature Invoice lacks | taxes and currencies live under `setting`; the rest do not exist here |
| `HTTP 404 Resource does not exist` | the id belongs to another org, or to Books rather than Invoice | re-fetch the id from a list call under the same org header |
| "… is mandatory" / "Please enter a valid …" | the message names the field | ids are strings, dates `yyyy-MM-dd`, money numbers |
| cannot edit or delete an invoice | it is sent, or a payment is applied | `invoice list-all-payments <id>`, unapply, then void or credit-note instead |
| totals disagree with what you computed | you computed money | send line items and `tax_id`; read back what Zoho calculated |
| a back-dated write succeeds and should not have | Invoice has no period lock to stop it | check the date with whoever closes the books — the API will not |
| exit 3 | not signed in | a human runs `zone login invoice` |
| exit 4 | token lacks `ZohoInvoice.fullaccess.all` | a human re-consents that service |
| exit 7 / "too many requests" | per-minute and per-day caps per org | back off; filter lists rather than fetching everything |

> Command names, groups, paths, HTTP verbs, positional arguments, the `--flag` surface, the missing Books groups and the `X-com-zoho-invoice-organizationid` header all come from the installed specs and zone's service registry, so they are exact. The JSON field names inside the example bodies follow Zoho's Invoice v3 documentation and were **not** re-verified live — this account has no Invoice session (`zone status` reports invoice as not signed in). Run `zone login invoice` and every payload above can be confirmed against a real organization.
