### Zoho Sprints — teamId typed on every command, form bodies the CLI currently refuses, and a vocabulary you must fetch before you can use it

Four things cause most failures here: `teamId` is a **required positional on every command** and stored context does not fill it; most calls need three or four ids in the right order; **54 of the 61 form-body writes reject their own `--form`** and have to go through `zone api`; and story/task/bug, statuses and priorities are not enum literals — they are per-project rows you list first.

#### teamId is typed, not stored

```bash
zone sprints team list                       # workspaces — teamId comes from here
zone sprints team settings <teamId>
zone sprints project list <teamId> --index 1 --range 100
```

`zone ctx sprints teamId=<id>` is accepted but never fires: `teamId` is declared as a positional argument on every command but `team list`, so the parser demands it before context is consulted. Reproduced locally:

```
$ zone sprints project list
error: missing required argument 'teamId'      (exit 2)
```

`team list` takes no ids at all, which makes it the cheap auth probe. Sprints is an **extended-tier** service, so `zone login --all` skips it — the human must name it: `zone login sprints`. There is no fullaccess scope; the consent is 86 granular ones.

#### The hierarchy decides how many ids you type

team → project → sprint → item → checklist group → checklist entry.

```bash
zone sprints sprint list <teamId> <projectId> --index 1 --range 50
zone sprints item list <teamId> <projectId> <sprintId>
zone sprints item get <teamId> <projectId> <sprintId> <itemId>
zone sprints checklist list <teamId> <projectId> <sprintId> <itemId> <clgroupId>
```

Backlog items are not a separate endpoint. `zone sprints project backlog <teamId> <projectId>` returns the backlog's id and you pass **that as the sprintId**:

```bash
zone sprints item list <teamId> <projectId> <backlogId>
```

Epics and releases hang off the project (`zone sprints epic list <teamId> <projectId>`), but `epic associate-items` takes a **sprintId**, because it works through that sprint's item list. Two paths deliberately break the pattern: `checklist edit` has **no itemId segment** (`<teamId> <projectId> <sprintId> <clgroupId> <clitemId>`), and `extension item-data-get` and its siblings address an item **without a sprintId**. Assuming the regular shape there produces a 404, not a validation error.

#### Two read modes, and a second paging style for test management

Collections are `?action=data` with `--index` (1-based) and `--range` (page size), baked into the path; detail reads are `?action=details` — `sprint list` and `sprint get` are the same URL with a different action.

```bash
zone sprints item list <teamId> <projectId> <sprintId> --index 1 --range 100 --toon
zone sprints item get-many <teamId> <projectId> --item-ids '["<id1>","<id2>"]'
zone sprints portfolio view <teamId> <viewType> <moduleId> <viewNumber>   # cross-project reads
```

Test management pages differently: `test-case list`, `test-case activity`, `test-suite list`, `test-suite cases` and `test-run cases` take a **required `--range`** plus a cursor (`--cursor`, but `--next` on `test-suite list`), and `test-run list` requires `--type`. Carrying `--index` over from the other groups is the usual cause of a 400 there.

#### The vocabulary is data, not constants

There is no `"Bug"` or `"In Progress"` string to send:

```bash
zone sprints item-type list <teamId> <projectId>      # story / task / bug live here, with ids
zone sprints item-status list <teamId> <projectId>
zone sprints project priorities <teamId> <projectId>
zone sprints link-type list <teamId>                  # blocks / duplicates …, each with a reverse name
zone sprints tag list <teamId>                        # tags are workspace-wide, not per project
zone sprints module list <teamId>                     # module ids — comments and layouts need one
zone sprints field list <teamId> <moduleId>           # custom fields; field project-list for one project
```

`item create` wants the type as `projitemtypeid`, so read the types for **that project** — item types, statuses and priorities are project-scoped, tags and link types are workspace-scoped. Comments are one generic endpoint across record kinds: `zone sprints comment list <teamId> <projectId> <moduleId> <entityId>`, where `moduleId` selects whether the entity is a sprint, item, meeting or expense.

#### Writes: form-encoded classic modules, JSON for the newer ones

The classic modules — project, sprint, item, epic, release, meeting, user, tag, comment, checklist, timesheet, webhook, custom records — take **urlencoded form bodies** and update with **POST, not PUT**. The only PUT/PATCH rows in the whole service are in `extension`, `okr` and the `test-*` groups, which take JSON via `--data`. `release update` also carries an extra `/update/` path segment that its siblings do not.

Many classic endpoints multiplex on an `action` form field, so the URL alone does not identify the operation: `linkitems` vs `editlinkitem`, `approvelogs` vs `updatebillingstatus`, `moveitem`, `associateupdate`, `updatefollowers`, `addnotes`/`updatenotes`, `addclgroup`/`editclgroup`, `changestatus`, `adduser`, `role`. Array parameters travel as JSON strings — `itemidarr`, `rootitemidarr`, `logidarr`, `expenseids`.

#### The form-body commands that will not run

Reproduced locally against this build: 54 of the 61 form-body write commands reject the only body flag they offer.

```
$ zone sprints tag create <teamId> --form action=createtag --form name=Blocked
error: This command requires a request body.
  hint: Pass --data '<json>' (or --data @file.json).      (exit 2)
```

`--data` is not registered on those commands — `--help` lists only `--form` — so the hint cannot be followed. Reach the endpoint through the proxy instead; same session, same base URL, same form encoding:

```bash
zone api sprints POST /team/<teamId>/tag/ --form action=createtag --form name=Blocked --form color=#3CB371
zone api sprints POST /team/<t>/projects/<p>/sprints/<s>/item/<i>/timesheet/ --form action=additemlog --form duration=1h
```

Affected across the board: every `comment`, `checklist`, `checklist-group`, `webhook`, `meeting`, `item-status`, `release`, `release-stage`, `user`, `record` and `timesheet` write, plus `project update`, `sprint update`, `epic update`, `tag create/update`, `item move`, `item link`, `item set-tags`, `item set-followers` and the attachment adds. The seven that do work as typed commands are `project create`, `sprint create`, `sprint cancel`, `item create`, `item update`, `epic create` and `expense stop-recurrence`:

```bash
zone sprints item create <teamId> <projectId> <sprintId> --form name=Checkout-500 --form projitemtypeid=<id>
zone sprints item update <teamId> <projectId> <sprintId> <itemId> --form ownerid=<userId>
```

DELETE commands and the JSON-body groups are unaffected — `item delete`, `okr create --data '<json>'` and `test-case create --data '<json>'` behave normally.

#### Deleting, and what is reversible instead

Every `delete` here is an immediate DELETE; no bin is exposed. `project delete`, `sprint delete`, `epic delete`, `release delete`, `item delete`, `test-case delete-bulk --ids '["<id>"]'`, `expense delete --expenseids '["<id>"]'`, `timesheet delete --logidarr '["<id>"]'`. The sprint lifecycle is the reversible layer: `sprint start`, `sprint complete`, `sprint cancel`, `sprint reopen` and `sprint replan` move a sprint between states, and `item move` relocates work rather than recreating it.

#### Errors → what to do

| What you see | Meaning | Do |
|---|---|---|
| `missing required argument 'teamId'` | context never fills it | pass it as the first positional; get it from `zone sprints team list` |
| `This command requires a request body` after `--form …` | the guard demands `--data`, which that command does not offer | same path via `zone api sprints POST … --form k=v` |
| `Use either --data or --form, not both` | one body per request | pick the one the endpoint documents |
| 404 on an item, checklist or comment path | ids are scoped to their parent — the sprintId in the path must own the item | re-fetch from `zone sprints item list` for that sprint |
| 400 on a `test-*` list | `--range` is required there and paging is cursor-based | add `--range`, then follow `--cursor` (`--next` on `test-suite list`) |
| a shared endpoint doing the wrong thing | it multiplexes on `action` and none was sent | add the `action=` field the command's description names |
| 405 on `okr update` | the docs badge it PATCH but their own example posts | retry the same body as POST via `zone api sprints` |
| exit 4 | 86 granular scopes and no fullaccess — one family is missing | human re-runs `zone login sprints` |
| exit 3 | not signed in; extended-tier, so `zone login --all` skipped it | human runs `zone login sprints` by name |

> Command names, paths, HTTP verbs, positional arguments, required flags, the extended tier and the scope list come from the installed specs and zone's registry, so they are exact. The `--form` guard failure and the `missing required argument 'teamId'` error were **reproduced locally** against this build — both surface client-side, before any request leaves. Response fields, the `action=` values, the payload keys and the `okr update` method mismatch follow Zoho's Sprints documentation as recorded in those specs and were **not** verified against a live workspace — this account has no Sprints session. Run `zone login sprints` and they can be confirmed.
