### Zoho SalesIQ — the screen name *is* the path, four different paging styles, and writes that reach real customers

Four things cause most failures here: nearly every path begins `/api/v2/<screen_name>/…` so nothing works until ctx is set, the **same value is a ctx variable in most groups and a positional argument in `portal`**, paging changes shape from resource to resource, and several of the write commands put text in front of a live visitor.

#### The screen name goes into the path

```bash
zone salesiq portal list                      # the only bootstrap call — needs no ctx
zone ctx salesiq screen_name=<name>           # fills :screen_name on ~180 commands
zone salesiq operator list
```

Without it the path is malformed rather than unauthorized, so the failure looks like a bad URL, not a permission error. Three families skip it entirely: `/api/v2/portals` (`zone salesiq portal list`), `/api/v2/encryptions` (`zone salesiq encryption encrypt`) and the broadcast invoke URL (`zone salesiq outbound broadcast`).

The `portal` group is the exception that catches people: `zone salesiq portal get <screenName>`, `portal update` and `portal change-owner` take the screen name **positionally** (`/api/v2/portals/:screenName`), while every other group reads the identical value out of ctx. Same string, two delivery mechanisms.

#### Operators, visitors, conversations — three actors, three surfaces

| | Who | Read it with | Paging |
|---|---|---|---|
| **operator** | your staff | `zone salesiq operator list` | `--index` (20 per fetch) |
| **visitor** | the person on the website | `zone salesiq visitor list` | keyset `--prev-key` / `--next-key`, `--limit` max 200 |
| **conversation** | the chat between them | `zone salesiq conversation list` | `--limit` max 99 + `--page` |

`visitor list` is the odd one out in two ways: it is on **`/api/v3/`**, not v2, and it returns nothing until "Basic visitor information" is enabled in the portal's API permissions — an empty list there usually means the setting, not an empty site.

Departments are the scoping unit underneath all three. Operators are attached to them (`zone salesiq operator add-departments`, `zone salesiq department add-operators`), and conversations, articles, routing rules and canned responses all filter by `--department-id`. When a read comes back thinner than the UI shows, check which department you are scoped to before assuming the data is missing.

A conversation is not flat: `zone salesiq conversation transcript <id>` returns its messages, while `conversation threads <id>` and `conversation thread <id> <threadNumber>` split a long-running chat into visits. Calls, feedback and attachments hang off both levels — `zone salesiq call of-conversation <id>`, `call of-thread <id> <threadNumber>`, `zone salesiq feedback of-conversation <id>`.

#### Paging is four different mechanisms

- `--limit` + `--page` — conversations, articles, conversation threads.
- `--limit` + `--index` — operators, departments, calls, blocked IPs, chat monitors, bots, feedback of a conversation.
- `--limit` + `--pageno` — webhook failure executions only.
- keyset `--prev-key` / `--next-key` (epoch ms) — the v3 visitor list.

And one genuine oddity: **`zone salesiq canned-response list` returns `limit + 1` rows** — the extra row is the signal that another page exists, not a real record. Trim it before counting.

All time filters are **epoch milliseconds** (`--from-time`, `--to-time`). Ids are long numeric strings; the exceptions are webhook execution ids (UUIDs) and visitor-side conversation ids (encrypted `siq…` strings).

#### Enable, disable and reorder are usually a payload, not an endpoint

`operator` and `department` have real `enable` / `disable` commands:

```bash
zone salesiq operator disable <id>
zone salesiq department enable <id>
```

Everything rule-shaped does **not**. Lead-score rules, visitor-routing rules, chat-routing rules, triggers and tracking presets are switched on or off and reordered by PUTting a discriminating payload to the ordinary update URL:

```bash
zone salesiq leadscore update <id> --data '{"status":"disable"}'
zone salesiq trigger update <id> --data '{"status":"enable"}'
```

Searching the command list for `enable` in those groups finds nothing, and that absence is the design, not a gap in coverage. The valid field names for the discriminating payload come from Zoho's docs — check one rule with `get` first and mirror what it returns.

Building a rule's conditions starts from `zone salesiq criteria leadscore`, `criteria visitor-routing`, `criteria chat-routing`, `criteria trigger-rules` — these return the fields and operators the rule engine will actually accept, which is cheaper than guessing at a condition JSON.

#### Writes that a customer sees

These are not internal-state changes:

```bash
zone salesiq conversation send-message <id> --data '{…}'      # posts as an operator into a live chat
zone salesiq conversation send-to-visitor <email> --data '{…}' # into that visitor's most recent active chat
zone salesiq outbound send --data '{…}'                        # proactive / WhatsApp template message
zone salesiq outbound broadcast <encString> --data '{…}'       # fires a whole WhatsApp broadcast, consumes credits
zone salesiq visitor-chat send-message <encId> --data '{…}'    # writes AS the visitor, on the /api/visitor/v1 surface
```

`conversation pickup`, `close`, `close-missed`, `assign-missed` and `transfer` change who is responsible for a live chat and are visible to the operators involved. `zone salesiq outbound templates` lists what may be sent before you send anything.

#### Deletes

```bash
zone salesiq conversation delete --channel website --data '{"ids":["…"]}'
```

That one is unusually fussy and worth reading twice: it needs the `--channel` header flag (`website|ios|android|emailsignature`), it only accepts **closed** chats, ids go in the body rather than the path, and it is a soft delete into a 90-day Recycle Bin.

Elsewhere: `zone salesiq article-category delete <id> --configure-to <id>` requires a destination category for the orphaned articles; `zone salesiq tag remove-from <module> <recordId> --id <tagId>` needs the tag id as a flag; and `zone salesiq webhook remove-apps <id>` removes **all** apps from a webhook, not a selected few.

#### Rate behaviour

Bursts of parallel requests trip an **IP-level lockout, error 1023** — not a per-minute quota you can wait out cheaply. Serialize loops over conversations or operators, and prefer one filtered `conversation list` over N per-record `get` calls.

#### Errors → what to do

| What you see | Meaning | Do |
|---|---|---|
| a malformed-URL or 404 on every call | `screen_name` is missing from ctx | `zone ctx salesiq screen_name=<name>` (from `zone salesiq portal list`) |
| `portal get` rejects your ctx value | that group takes the screen name positionally | `zone salesiq portal get <screenName>` |
| error 1023, suddenly on everything | IP lockout from parallel bursts | stop, back off, then run the calls serially |
| `visitor list` returns nothing on a busy site | "Basic visitor information" is off in the portal's API permissions | enable it in the portal settings |
| one extra row in a canned-response page | `limit + 1` is the next-page signal | drop the last row before counting |
| a `--page` flag that does nothing | that resource pages by `--index`, `--pageno` or keyset | check the command's own flags |
| no `enable`/`disable` command for a rule | those are payload-discriminated PUTs | `zone salesiq leadscore update <id> --data '{"status":…}'` |
| `conversation delete` rejected | the chat is not closed, or `--channel` is missing | close it first; pass `--channel` |
| exit 3 | not signed in — SalesIQ is its own consent | human runs `zone login salesiq` |
| exit 4 | the token lacks that granular `SalesIQ.*` scope | human re-logs in that service |

> Command names, groups, paths, HTTP verbs, required flags and header flags, the `:screen_name` path substitution and the `portal` group's positional exception, the four paging styles and their documented caps (99 conversations, 200 visitors, 20 operators per fetch), the `limit + 1` canned-response page signal, the `conversation delete` rules and the 1023 lockout all come from the installed specs, so they are exact. Request and response **field** names in the example payloads follow Zoho's SalesIQ v2 documentation and were **not** re-verified live — this account has no SalesIQ session. Run `zone login salesiq` and confirm them with a `get` on one record before writing.
