node-autotask 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/MCP_TOOL_CATALOG.json +153 -1269
  3. package/MCP_TOOL_MANIFEST.md +28 -95
  4. package/MCP_TOOL_OVERRIDES.json +8 -62
  5. package/N8N_MCP_MANIFEST.json +118 -695
  6. package/N8N_MCP_MANIFEST.md +32 -52
  7. package/README.md +5 -10
  8. package/capabilities.json +23788 -31075
  9. package/dist/{base-CftidiyZ.d.cts → base-BVoYDlVA.d.cts} +2 -2
  10. package/dist/{base-CftidiyZ.d.ts → base-BVoYDlVA.d.ts} +2 -2
  11. package/dist/capabilities.cjs +2 -2
  12. package/dist/capabilities.js +1 -1
  13. package/dist/{chunk-N6IWPGHB.js → chunk-ASMCTZO2.js} +23787 -31074
  14. package/dist/{chunk-6WLGHKIU.cjs → chunk-DC3PY4EF.cjs} +1839 -1407
  15. package/dist/{chunk-BKTOMNT2.js → chunk-DXKXQHCX.js} +5 -5
  16. package/dist/{chunk-KWQSGWY5.cjs → chunk-JO6WI763.cjs} +143 -143
  17. package/dist/{chunk-LYI5FRPX.js → chunk-NSW7JPVS.js} +11544 -13365
  18. package/dist/{chunk-7MCKJTSA.js → chunk-O4PIPUS4.js} +1583 -1151
  19. package/dist/{chunk-PH3ESYAF.cjs → chunk-VXVYFJH3.cjs} +23787 -31074
  20. package/dist/{chunk-EMYDC5SG.cjs → chunk-YMBVKVK4.cjs} +6123 -7944
  21. package/dist/{index-YbEk0awP.d.cts → index-C5g1H_hF.d.ts} +1194 -1415
  22. package/dist/{index-D8FtzUGP.d.ts → index-Db0SNnQn.d.cts} +1194 -1415
  23. package/dist/{index-DgBA1JMH.d.cts → index-c549LvOb.d.cts} +188 -188
  24. package/dist/{index-DgBA1JMH.d.ts → index-c549LvOb.d.ts} +188 -188
  25. package/dist/index.cjs +22 -22
  26. package/dist/index.d.cts +15 -42
  27. package/dist/index.d.ts +15 -42
  28. package/dist/index.js +7 -7
  29. package/dist/operations/index.cjs +5 -2327
  30. package/dist/operations/index.d.cts +7 -287
  31. package/dist/operations/index.d.ts +7 -287
  32. package/dist/operations/index.js +29 -2351
  33. package/dist/pool.cjs +8 -8
  34. package/dist/pool.d.cts +1 -1
  35. package/dist/pool.d.ts +1 -1
  36. package/dist/pool.js +4 -4
  37. package/dist/resources/index.cjs +4 -4
  38. package/dist/resources/index.d.cts +2 -2
  39. package/dist/resources/index.d.ts +2 -2
  40. package/dist/resources/index.js +7 -7
  41. package/dist/types/index.d.cts +1 -1
  42. package/dist/types/index.d.ts +1 -1
  43. package/docs/API.md +201 -303
  44. package/docs/LIVE-TESTING.md +4 -6
  45. package/docs/N8N-ADAPTER.md +26 -26
  46. package/n8n-mcp.curation.json +3 -3
  47. package/package.json +1 -1
package/docs/API.md CHANGED
@@ -260,9 +260,9 @@ status → what the vendor does that triggers it.
260
260
  | `QueryTooLongError` | `QUERY_TOO_LONG` | policy | — | caller-requested `via: 'get'` whose serialized URL would exceed the 2048-char GET cap — the SDK never silently degrades to POST |
261
261
  | `StaleObjectError` | `STALE_OBJECT` | conflict | — | the caller-supplied `lastModifiedDate` no longer matches the vendor's current value (no `If-Match`/ETag exists) |
262
262
  | `ConfirmationRequiredError` | `CONFIRMATION_REQUIRED` | policy | — | a destructive / approval-gated op invoked without its exact confirmation flag (`invoke` governor, section 7) |
263
- | `DeleteBoundaryError` | `DELETE_BOUNDARY` | policy | — | `operations.recovery` `action: 'delete'` on a resource the vendored spec defines no DELETE for — refused PRE-SEARCH (zero wire; the boundary is the spec-derived plan audit, never a raw vendor 405; issue #9) |
264
- | `MarkerUnsafeIdError` | `MARKER_UNSAFE_ID` | policy | — | a `operations.recovery` match whose id is beyond `Number.MAX_SAFE_INTEGER` (unaddressable through the typed delete surface) — refused with the typed code, never a silent skip (issue #9, P1) |
265
- | `RecoveryTruncatedError` | `RECOVERY_TRUNCATED` | policy | — | the `operations.recovery` marker search tripped its match cap (more matches than `limit`) — refused instead of a silent truncation / silent empty (issue #9) |
263
+ | `DeleteBoundaryError` | `DELETE_BOUNDARY` | policy | — | a delete on a resource the vendored spec defines no DELETE for — refused PRE-SEARCH (zero wire; the boundary is the spec-derived plan audit, never a raw vendor 405; issue #9). The throwing op (`operations.recovery`) was deleted in #289 — the refusal code stays in the write-outcome taxonomy |
264
+ | `MarkerUnsafeIdError` | `MARKER_UNSAFE_ID` | policy | — | a marker-filtered match whose id is beyond `Number.MAX_SAFE_INTEGER` (unaddressable through the typed delete surface) — refused with the typed code, never a silent skip (issue #9, P1; the throwing op was deleted in #289 — the refusal code stays) |
265
+ | `RecoveryTruncatedError` | `RECOVERY_TRUNCATED` | policy | — | the marker-filtered bounded search tripped its match cap (more matches than `limit`) — refused instead of a silent truncation / silent empty (issue #9; the throwing op was deleted in #289 — the refusal code stays) |
266
266
  | `AutotaskError` | `BUDGET_EXCEEDED` | policy | — (the call that would exceed it is not sent) | `operations.revenueRollup`: the pre-walk estimate exceeds `maxVendorCalls` (refused before any stream; `details: { phase: 'estimate', estimate, budget, suggestions }`) or the counting wrapper reached the budget mid-walk (`phase: 'walk'`; the partial fold is discarded) — `suggestedAction` names the narrowing (issue #234) |
267
267
 
268
268
  Retryable by contract: `NETWORK_ERROR`, `TIMEOUT`, `RATE_LIMITED`,
@@ -287,8 +287,7 @@ retryability: `emptyReason` (the pinned typed empty-reason — the miss's
287
287
  kind + note + authority + remedy; §11.10) and `pointerNote` (the
288
288
  known-unusable-op pointer sentence — the status/code are untouched, the
289
289
  note routes to the operation that works), plus the additive refusal /
290
- drift payloads (the `operations.recovery` `candidateIds` refusal, the #13
291
- shape-drift marker info). A non-pinned surface gets no `details` at all
290
+ drift payloads (the #13 shape-drift `candidateIds` refusal). A non-pinned surface gets no `details` at all
292
291
  (no speculation). On the MCP tools' served error payload, `details` is
293
292
  passed through verbatim (live-verified 2026-10-04: the W-D3 pointer note
294
293
  rides a real `time_entries.getPosted` vendor 500 on the wire; that 500 is gone since 0.32.0, the pointer-note mechanism is unchanged) and the
@@ -1132,101 +1131,44 @@ be cross-mode-refused.
1132
1131
  all 1281 ops × 3 modes, zero wire).
1133
1132
 
1134
1133
  <!-- BEGIN GENERATED:approval-policy -->
1135
- **Harness approval policy (recommended).** Require approval on every tool whose MCP annotation is `readOnlyHint !== true` — today exactly **267 tools**: the 265 dedicated write/delete tools (209 `effect=write` + 56 `effect=destructive`, every one of them has a dedicated tool) + `autotask_write` + `autotask_delete`. The remaining **636 tools** (the 623 dedicated read tools + the 10 core read tools + `autotask_read` + `autotask_catalog` + `autotask_describe`) all carry `readOnlyHint: true` and are safe ungated: every op they can route is `effect=read` (zero mutating requests). Refined class for the highest-risk ops: the **57 tools with `destructiveHint: true`** (56 dedicated delete tools + `autotask_delete`) additionally require `confirm === <operation key>` even inside `autotask_delete`. The approval-gated writes (`configuration_items.moveConfigurationItem`, `contacts.moveToCompany`, `resources.transferOwnership`) require the same confirm inside `autotask_write`. Migration: `autotask_invoke` is REPLACED by `autotask_read`/`autotask_write`/`autotask_delete` — update harness config and remove any `autotask_invoke` entry. The rule is derivable from the registry `effect` column and the mode tools’ fixed annotations; it never needs a hand-maintained name list.
1134
+ **Harness approval policy (recommended).** Require approval on every tool whose MCP annotation is `readOnlyHint !== true` — today exactly **246 tools**: the 244 dedicated write/delete tools (188 `effect=write` + 56 `effect=destructive`, every one of them has a dedicated tool) + `autotask_write` + `autotask_delete`. The remaining **599 tools** (the 586 dedicated read tools + the 10 core read tools + `autotask_read` + `autotask_catalog` + `autotask_describe`) all carry `readOnlyHint: true` and are safe ungated: every op they can route is `effect=read` (zero mutating requests). Refined class for the highest-risk ops: the **57 tools with `destructiveHint: true`** (56 dedicated delete tools + `autotask_delete`) additionally require `confirm === <operation key>` even inside `autotask_delete`. The approval-gated writes (`configuration_items.moveConfigurationItem`, `contacts.moveToCompany`) require the same confirm inside `autotask_write`. Migration: `autotask_invoke` is REPLACED by `autotask_read`/`autotask_write`/`autotask_delete` — update harness config and remove any `autotask_invoke` entry. The rule is derivable from the registry `effect` column and the mode tools’ fixed annotations; it never needs a hand-maintained name list.
1136
1135
  <!-- END GENERATED:approval-policy -->
1137
1136
 
1138
- ### 7.5 `recovery(client, input, opts?)` — the one-call recovery/cleanup op (issue #9)
1137
+ ### 7.5 the `create` dry-run delete-boundary warning (issue #9)
1139
1138
 
1140
1139
  The delete-boundary reality (§11.1, §11.9): the vendored spec defines NO
1141
1140
  DELETE on many entity paths — most of them the campaign's own resources. A
1142
1141
  `create` that cannot be undone through the API leaves the created record as
1143
- permanent residue once its id is lost (P1). `recovery` is the typed one-call
1144
- path out — a cross-resource special op in the `operations` group (effect
1145
- `write`, the governor's dry-run-first applies; `mcpTool`
1146
- `autotask_recovery`, tier `extended`):
1142
+ permanent residue once its id is lost (P1). The one-call recovery op that
1143
+ turned the boundary into a typed, bounded path (the marker-filtered find +
1144
+ confirm-gated delete that shared the SAME generated boundary note) was
1145
+ deleted in #289 — no server consumer; the boundary data, the typed error
1146
+ codes and the dry-run warning remain (§11.9).
1147
1147
 
1148
- ```ts
1149
- import { recovery } from 'node-autotask/operations';
1150
- // find the scratch record a write just stamped (ONE bounded query, zero mutating wire):
1151
- const r = await recovery(client, { resource: 'companies', marker: 'FULL100W-1791110339' });
1152
- // r.delete.available === false + r.cleanup.note — the spec says the vendor has no DELETE.
1153
- // where the vendor DOES allow it (delete available):
1154
- const dry = await recovery(client, { resource: 'configuration_item_dns_records', marker: 'm', field: 'data', parentId: 42, action: 'delete' });
1155
- const applied = await recovery(client, { resource: 'configuration_item_dns_records', marker: 'm', field: 'data', parentId: 42, action: 'delete' },
1156
- { dryRun: false, confirm: 'operations.recovery' });
1157
- ```
1158
-
1159
- - **`find`** (the default — a read): ONE bounded marker-filtered query
1160
- (server-side pagination; default `limit` 25, max 100) returns the matched
1161
- records' ids (`matches: [{ id, value }]`) + the delete-boundary fact
1162
- (`delete: { available, reason, authority }`) + the UI-only `cleanup` note.
1163
- Zero mutating wire.
1164
- - **`delete`** (destructive, dry-run-first): the dry run performs the search
1165
- and reports the match set with **zero DELETE wire** (`simulated: true`);
1166
- the apply requires the exact confirm string (`confirm ===
1167
- 'operations.recovery'` — the governor forwards the caller's `confirm`
1168
- through the trailing bag) and issues one DELETE per safe match id + one
1169
- verification read per id (`deleted: [{ id, verified, code? }]`).
1170
- - **the marker field**: the `contains` filter runs on the name-like field
1171
- the write stamped — default the plan's `markerField` for the resource
1172
- (the spec-derived deterministic priority over the vendored model's field
1173
- set), pass `field` to override; REQUIRED when the resource has no
1174
- `markerField` (a `CONFIG_ERROR` names the candidates).
1175
- - **P1 discipline — never a silent failure mode**:
1176
- - a delete on a delete-less resource refuses **PRE-SEARCH** with the typed
1177
- `DELETE_BOUNDARY` (zero wire — the boundary is the local spec audit,
1178
- never a raw vendor 405);
1179
- - a match whose id is beyond `Number.MAX_SAFE_INTEGER` is never a silent
1180
- skip: `delete` refuses with the typed `MARKER_UNSAFE_ID` (zero DELETE
1181
- wire); `find` marks the match `id: null` + `unsafeId: true`;
1182
- - a cap-tripped search (more matches than `limit`) refuses with the typed
1183
- `RECOVERY_TRUNCATED` — never a silent truncation, never a silent empty;
1184
- - an unknown `resource` refuses with the typed `RESOURCE_UNKNOWN` (the
1185
- same code `operations.relationshipMap` uses: carries the resource count
1186
- + the `autotask_catalog` pointer).
1187
- - **the create dry run warns**: on a delete-less resource,
1148
+ - **The create dry run warns**: on a delete-less resource,
1188
1149
  `client.<resource>.create(data, { dryRun: true })` carries the
1189
1150
  report-only `delete-boundary` check (`ok: true` — `wouldApply` is
1190
1151
  preserved) + the boundary note in `warnings[]` (the SAME generated note
1191
- `recovery` serves — the describe and dry-run channels cannot drift).
1192
- Pinned by `test/operations/recovery.test.ts` (11 tests) + the full-surface
1193
- governor suite (`test/operations/invoke/modes.test.ts`).
1152
+ the describe channel carries — the describe and dry-run channels cannot
1153
+ drift). Pinned by `test/resources/mutation-safety.test.ts` + the
1154
+ full-surface governor suite (`test/operations/invoke/modes.test.ts`).
1194
1155
 
1195
- ### 7.6 `fetchMany(client, items, opts?)` / `fetchByIds(client, items, opts?)` — bulk fetch by ids
1156
+ ### 7.6 `fetchByIds(client, items, opts?)` — bulk fetch by ids
1196
1157
 
1197
- Two cross-resource bulk reads with per-item typed results — one entry per
1158
+ The cross-resource bulk read with per-item typed results — one entry per
1198
1159
  input occurrence: `{ resource, id, found, value, error?: { code, httpStatus?,
1199
1160
  message } }`; a NOT_FOUND or fault on one item never aborts the rest; the
1200
- result is `{ results, ok, failed }`. Both refuse pre-request (zero wire):
1161
+ result is `{ results, ok, failed }`. The op refuses pre-request (zero wire):
1201
1162
  non-numeric string ids (pointing at `operations.resolveAny` — canonical
1202
1163
  integer strings above 2^53 pass with their exact digits), parent-scoped
1203
1164
  children (their `get` needs `parentId`), unknown `fields` keys (the complete
1204
1165
  problem list in one message), unsafe number ids (the canonical-string
1205
1166
  pointer). The op never fabricates rows: values come only from a vendor 2xx
1206
1167
  (query row or GET record); a miss is a typed outcome, never a null-padded
1207
- row.
1208
-
1209
- - **`fetchMany`** — up to 20 items (mixed resources; the pinned
1210
- `FETCH_MANY_MAX_ITEMS`; over → `CONFIG_ERROR` pre-request, zero wire),
1211
- one sequential GET per item. Per-item `fields?: string[]` is a client-side
1212
- projection (the GET returns the full model; projection is post-fetch, zero
1213
- extra wire).
1214
- **Live field validation (opt-in `opts.liveFields`, issue #66)**: when
1215
- `true`, each item's `fields` validate against the tenant's **live** entity
1216
- field metadata (the existing cached metadata channel — with
1217
- `fieldCache` configured a warm cache costs zero wire; a cold run is one
1218
- `entityInformation/fields` GET per DISTINCT resource in the batch) instead
1219
- of the vendored-spec field set alone. A resource whose metadata is
1220
- unavailable (network fault, vendor 5xx) falls back to the vendored-spec
1221
- set for THAT resource only — unavailability never throws and never blocks
1222
- the batch. When available the live set is authoritative: a field it lacks
1223
- is refused even if the spec lists it, and a live field the spec lacks is
1224
- accepted — a tenant whose live TimeEntry entity exposes `postedDate` (the
1225
- live record carries it even when the vendored model omits it) may project
1226
- it once `liveFields: true`; the default spec-only path still refuses it.
1227
- The default (no `liveFields`) is today's vendored-spec-only validation,
1228
- bit-for-bit, zero metadata wire. Field discovery in both modes:
1229
- `operations.fieldCatalog`.
1168
+ row. The per-item result shapes and the pre-request validation taxonomy are
1169
+ reused from the deleted `fetchMany` op (issue #289 — the shared `FetchMany*`
1170
+ result types kept their names).
1171
+
1230
1172
  - **`fetchByIds`** — up to 500 items (mixed resources; the pinned
1231
1173
  `FETCH_BY_IDS_MAX_ITEMS`; `opts.maxItems` may lower the cap, never raise
1232
1174
  it; over → `CONFIG_ERROR` pre-request, zero wire). **Query-first**: per
@@ -1241,8 +1183,7 @@ row.
1241
1183
  limited to the plan-pinned blind surfaces). Wire cost: per resource
1242
1184
  `ceil(uniqueIds/250)` query calls + one `get` per query-missed id — a
1243
1185
  clean 500-id single-resource batch is **2 wire** (down from 500 sequential
1244
- GETs); the blind-surface worst case is 2 + up to 500 gets (fetchMany
1245
- parity, never worse). A chunk-level vendor fault faults every item of that
1186
+ GETs); the blind-surface worst case is 2 + up to 500 gets. A chunk-level vendor fault faults every item of that
1246
1187
  chunk (the chunk's `code`/`httpStatus`) and the remaining chunks still run
1247
1188
  (input order is preserved across chunks and groups; a duplicate id gets its
1248
1189
  own entry with the identical outcome over one wire fetch).
@@ -1469,14 +1410,17 @@ notes cannot be deleted, so they never use create-then-delete.
1469
1410
 
1470
1411
  For per-ticket status / assignee / company use `operations.fetchByIds` on the group ticket ids
1471
1412
  (with `names: true`: `statusLabel`, `assignedResourceIDName`, `companyIDName`).
1472
- - **`tz` on the `dateWorked` ops** (`postedWithin`, `dateWorkedWindow`, `customRange` and the relative
1473
- windows): the vendor stores `dateWorked` as a date-only value at 00:00 UTC, so the wire bounds are always whole UTC days.
1474
- For `postedWithin` / `dateWorkedWindow` / `customRange` the `from`/`to` days ARE the days, so `tz` is
1475
- validated but never shifts them. For the relative windows (`today`, `last7`, `lastFullMonth`, ...) `tz` picks which
1476
- calendar dates the window covers in that zone and the bounds are whole UTC days of those dates. (Before 0.24.0 a `tz`
1477
- behind UTC, e.g. `America/Los_Angeles`, selected the NEXT UTC day; live-verified 2026-10-23.)
1413
+ - **`dateWorked` windows are whole UTC days (issue #66; consolidated in #290)**: the vendor stores
1414
+ `dateWorked` as a date-only value at 00:00 UTC, so the wire bounds are always whole UTC days.
1415
+ `time_entries.dateWorkedWindow` takes `from`/`to` as `YYYY-MM-DD` and the days ARE the bounds — the
1416
+ `tz` option is gone with the consolidation (before it, `tz` was validated but never shifted these
1417
+ bounds), and the relative windows (`today`, `last7`, `lastFullMonth`, ...) are cut. (Before 0.24.0 a
1418
+ `tz` behind UTC, e.g. `America/Los_Angeles`, selected the NEXT UTC day; live-verified 2026-10-23.)
1419
+
1420
+ ### 7.12 `timeEntries.approve` — time-entry approval levels (2026-10-26; cut in #292)
1478
1421
 
1479
- ### 7.12 `timeEntries.approve` — time-entry approval levels (2026-10-26)
1422
+ **Cut in #292** (confirmed unused write op — the CHANGELOG names the raw-composition
1423
+ alternative). The vendor facts below stand for anyone composing it by hand:
1480
1424
 
1481
1425
  `timeEntries.approve(id, opts?)` records one approval level for a time entry.
1482
1426
  The vendor keeps approvals in a separate entity (`BillingItemApprovalLevels`)
@@ -1608,9 +1552,8 @@ over a window, by account vertical": the registry op `operations.revenueRollup`
1608
1552
  label }`, absent otherwise). A label shared by several live picklist ids
1609
1553
  selects every one of them.
1610
1554
  - **`groupBy`** (default `['vertical', 'contractType']`): `vertical`
1611
- (`companies.marketSegmentID`), `contractType`, `contractCategory`,
1612
- `company`, `month` (the invoice month), `billingItemKind`, and the join
1613
- dimensions (#235):
1555
+ (`companies.marketSegmentID`), `contractType`, `company`, `month` (the
1556
+ invoice month), and the join dimensions (#235):
1614
1557
  - `contract` — `{ id, name }` (`contractName` joins the contract fetch).
1615
1558
  - `lineOfBusiness` — per billing line (`organizationalLevelAssociationID`;
1616
1559
  no contract-pointer fallback): three batch `id in` fetches (associations,
@@ -1621,26 +1564,18 @@ over a window, by account vertical": the registry op `operations.revenueRollup`
1621
1564
  their amount (LOB figures are floors). A row's `contracts` is "contracts
1622
1565
  with at least one line in this LOB", so per-LOB counts can sum to more than
1623
1566
  `totals.contracts`.
1624
- - `resource` — labour lines only (`billingItemType` Labor / Labor
1625
- Adjustment, or the FK fallback): `timeEntryID` → time entry → resource,
1626
- `{ id: resourceID, name: 'First Last' }`. Non-labour lines, and labour
1627
- lines with no time entry (e.g. an adjustment), are `{ id: null, label: 'n/a' }`.
1628
- - `billingCode` — `billingCodeID` → billing code `{ id, name }`; a line
1629
- without one is `{ id: null, label: 'none' }`.
1630
1567
 
1631
1568
  Each row key is `{ id, label?, name?, division? }`: picklist text in
1632
- `label`; vendor entity text (untrusted — company, contract, LOB level2,
1633
- resource and billing-code names) in `name`, and the LOB division (level1
1634
- name, also untrusted) in `division`. Missing values get their own row
1635
- (`unassigned`, `uncategorised`, `no LOB assigned`, `n/a`, `none`); a value
1636
- the picklist does not serve stays id-only and is listed in
1637
- `coverage.blindSurfaces`. A join id the `id in` fetch does not return is
1638
- never guessed: the line goes to `coverage.excluded.unresolvedAssociations`
1639
- (association or its level1/level2), `unresolvedTimeEntries` (time entry or
1640
- its resource) or `unresolvedBillingCodes`, with its amount. These buckets
1641
- (and `coverage.lineOfBusiness`) appear only when their dimension is grouped;
1642
- the line projection grows by `organizationalLevelAssociationID`,
1643
- `timeEntryID` / the type fields and `billingCodeID` the same way.
1569
+ `label`; vendor entity text (untrusted — company, contract and LOB level2
1570
+ names) in `name`, and the LOB division (level1 name, also untrusted) in
1571
+ `division`. Missing values get their own row (`unassigned`,
1572
+ `no LOB assigned`); a value the picklist does not serve stays id-only and is
1573
+ listed in `coverage.blindSurfaces`. A join id the `id in` fetch does not
1574
+ return is never guessed: the line goes to
1575
+ `coverage.excluded.unresolvedAssociations` (association or its level1/level2)
1576
+ with its amount. The bucket (and `coverage.lineOfBusiness`) appears only
1577
+ when its dimension is grouped; the line projection grows by
1578
+ `organizationalLevelAssociationID` the same way.
1644
1579
  - **Rows** are sorted by revenue descending, then ids; `top` (default 100,
1645
1580
  max 100) keeps the head and folds the tail into `rowsTruncated`. `totals`
1646
1581
  always cover every row.
@@ -1767,19 +1702,19 @@ const lob = await revenueRollup(client, { tenantWide: true, dateFrom: '2025-07-0
1767
1702
  ## 8. Resource surface
1768
1703
 
1769
1704
  <!-- BEGIN GENERATED:api-per-entity -->
1770
- Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page guarded), `queryBounded` (server-side pagination — one call walks the cursor until the total cap: `limit` (default 500 / max 2500), or independent `walk.maxRows` for exports (pages stay `min(500, limit)`); `hasMore` reports incomplete walks, and explicit walks also carry `truncated`; the MCP `query` surface, routed by `invoke`), `queryAll` (collect → `T[]` — the unbounded raw walk), `count`, `get` (id → record; `item: null` → `NotFoundError`), `create` / `update` / `delete` (where the vendor defines them — mutations take a `MutationOpts` bag: `{ dryRun, lastModifiedDate?, maxRetries? }` (issue #105: `maxRetries` overrides the configured retry budget for every request the mutation issues; `0` = exactly one send per request)), plus the helper floor where the plan declares it: `resolve` (all 121), `search` (40 — bounded contains on the resource text field), `getContext` (5 workflow resources), `findBy<Field>` (3), `createIfNotExists` (14). Entity metadata on every resource: `getEntityInfo`, `getFieldInfo`, `getUdfInfo` (UDF-capable 16). Parent-scoped children take `(parentId, id)` on the CRUD methods and require `parentId` on `createIfNotExists` (the scoped create POSTs the scoped collection; the dedup query stays global); `query`/`count`/`resolve`/`search` stay global (narrow with a `parentField` filter). Scoped `update` PATCHes the collection (the vendor keys the record by the id in the body).
1705
+ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page guarded), `queryBounded` (server-side pagination — one call walks the cursor until the total cap: `limit` (default 500 / max 2500), or independent `walk.maxRows` for exports (pages stay `min(500, limit)`); `hasMore` reports incomplete walks, and explicit walks also carry `truncated`; the MCP `query` surface, routed by `invoke`), `queryAll` (collect → `T[]` — the unbounded raw walk), `count`, `get` (id → record; `item: null` → `NotFoundError`), `create` / `update` / `delete` (where the vendor defines them — mutations take a `MutationOpts` bag: `{ dryRun, lastModifiedDate?, maxRetries? }` (issue #105: `maxRetries` overrides the configured retry budget for every request the mutation issues; `0` = exactly one send per request)), plus the helper floor where the plan declares it: `resolve` (122 of 129 — 7 lack it), `search` (41 — bounded contains on the resource text field), `getContext` (5 workflow resources); the `findBy<Field>` methods (findBySerial, findByTicketNumber, findByEmail) are class methods, not plan-declared special ops. Entity metadata on every resource: `getEntityInfo`, `getFieldInfo`, `getUdfInfo` (UDF-capable 16). Parent-scoped children take `(parentId, id)` on the CRUD methods and require `parentId` on `createIfNotExists` (the scoped create POSTs the scoped collection; the dedup query stays global); `query`/`count`/`resolve`/`search` stay global (narrow with a `parentField` filter). Scoped `update` PATCHes the collection (the vendor keys the record by the id in the body).
1771
1706
 
1772
1707
  | resource key | apiEntity | kind | tier | parent | standard ops present | special ops | mcpTool |
1773
1708
  |--------------|-----------|------|------|--------|----------------------|-------------|---------|
1774
1709
  | `appointments` | Appointment | entity | extended | no | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_appointment_get |
1775
1710
  | `billing_codes` | BillingCode | entity | extended | no | query, count, get, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_billingCode_get |
1776
1711
  | `billing_item_approval_levels` | BillingItemApprovalLevel | entity | extended | no | query, count, get, create | — | autotask_billingItemApprovalLevel_get |
1777
- | `billing_items` | BillingItems | entity | extended | no | query, count, get, update, resolve | customRange, getEntityInfo, getFieldInfo, getUdfInfo, last14, last24h, last30, last7, last90, lastFullFortnight, lastFullMonth, lastFullQuarter, lastFullWeek, today, yesterday | autotask_billingItems_get |
1778
- | `change_request_links` | ChangeRequestLink | entity | extended | no | query, count, get, create, delete, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_changeRequestLinks_get |
1712
+ | `billing_items` | BillingItems | entity | extended | no | query, count, get, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_billingItems_get |
1713
+ | `change_request_links` | ChangeRequestLink | entity | extended | no | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_changeRequestLinks_get |
1779
1714
  | `checklist_libraries` | ChecklistLibrary | entity | extended | no | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_checklistLibrary_get |
1780
1715
  | `checklist_library_checklist_items` | ChecklistLibraryChecklistItem | entity | extended | yes (checklist_libraries) | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_checklistLibraryChecklistItem_get |
1781
1716
  | `classification_icons` | ClassificationIcon | entity | advanced | no | query, count, get, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_classificationIcon_get |
1782
- | `companies` | Company | entity | core | no | query, count, get, create, update, resolve, search, getContext | fullProfile, getEntityInfo, getFieldInfo, getUdfInfo, prepareCrmWrite, searchByDomain, searchByIdentity, unbilledWork, upsertByName | autotask_company_get |
1717
+ | `companies` | Company | entity | core | no | query, count, get, create, update, resolve, search, getContext | fullProfile, getEntityInfo, getFieldInfo, getUdfInfo, prepareCrmWrite, searchByDomain, searchByIdentity, unbilledWork | autotask_company_get |
1783
1718
  | `company_alerts` | CompanyAlert | entity | extended | yes (companies) | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_companyAlert_get |
1784
1719
  | `company_locations` | CompanyLocation | entity | extended | yes (companies) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_companyLocation_get |
1785
1720
  | `company_notes` | CompanyNote | entity | extended | yes (companies) | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_companyNote_get |
@@ -1794,15 +1729,15 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1794
1729
  | `configuration_item_ssl_subject_alternative_names` | ConfigurationItemSslSubjectAlternativeName | entity | extended | yes (configuration_items) | query, count, get, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_configurationItemSslSubjectAlternativeName_get |
1795
1730
  | `configuration_item_types` | ConfigurationItemType | entity | extended | no | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_configurationItemTypes_get |
1796
1731
  | `configuration_item_webhooks` | ConfigurationItemWebhook | entity | extended | yes (configuration_items) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_configurationItemWebhook_get |
1797
- | `configuration_items` | ConfigurationItem | entity | core | no | query, count, get, create, update, resolve, search, getContext | createIfNotExists, findBySerial, getEntityInfo, getFieldInfo, getUdfInfo, moveConfigurationItem, profile | autotask_configurationItems_get |
1732
+ | `configuration_items` | ConfigurationItem | entity | core | no | query, count, get, create, update, resolve, search, getContext | findBySerial, getEntityInfo, getFieldInfo, getUdfInfo, moveConfigurationItem, profile | autotask_configurationItems_get |
1798
1733
  | `contact_group_contacts` | ContactGroupContacts | entity | extended | no | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contactGroupContacts_get |
1799
1734
  | `contact_groups` | ContactGroups | entity | extended | no | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contactGroups_get |
1800
1735
  | `contact_webhooks` | ContactWebhook | entity | extended | yes (contacts) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contactWebhook_get |
1801
- | `contacts` | Contact | entity | core | yes (companies) | query, count, get, create, update, delete, resolve, search, getContext | findByEmail, getEntityInfo, getFieldInfo, getUdfInfo, moveToCompany, upsertByEmail | autotask_contact_get |
1736
+ | `contacts` | Contact | entity | core | yes (companies) | query, count, get, create, update, delete, resolve, search, getContext | findByEmail, fullProfile, getEntityInfo, getFieldInfo, getUdfInfo, moveToCompany | autotask_contact_get |
1802
1737
  | `contract_billing_rules` | ContractBillingRule | entity | extended | yes (contracts) | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractBillingRule_get |
1803
1738
  | `contract_block_hour_factors` | ContractBlockHourFactor | entity | extended | yes (contracts) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractBlockHourFactor_get |
1804
1739
  | `contract_blocks` | ContractBlock | entity | extended | yes (contracts) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractBlock_get |
1805
- | `contract_charges` | ContractCharge | entity | extended | yes (contracts) | query, count, get, create, update, delete, resolve, search | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractCharge_get |
1740
+ | `contract_charges` | ContractCharge | entity | extended | yes (contracts) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractCharge_get |
1806
1741
  | `contract_exclusion_billing_codes` | ContractExclusionBillingCode | entity | extended | yes (contracts) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractExclusionBillingCode_get |
1807
1742
  | `contract_exclusion_roles` | ContractExclusionRoles | entity | extended | yes (contracts) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractExclusionRoles_get |
1808
1743
  | `contract_exclusion_set_excluded_roles` | ContractExclusionSetExcludedRoles | entity | extended | yes (contract_exclusion_sets) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractExclusionSetExcludedRole_get |
@@ -1818,22 +1753,22 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1818
1753
  | `contract_service_bundle_units` | ContractServiceBundleUnit | entity | extended | yes (contracts) | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractServiceBundleUnit_get |
1819
1754
  | `contract_service_bundles` | ContractServiceBundle | entity | extended | yes (contracts) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractServiceBundle_get |
1820
1755
  | `contract_service_units` | ContractServiceUnit | entity | extended | yes (contracts) | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractServiceUnit_get |
1821
- | `contract_services` | ContractService | entity | extended | yes (contracts) | query, count, get, create, update, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractService_get |
1756
+ | `contract_services` | ContractService | entity | extended | yes (contracts) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractService_get |
1822
1757
  | `contract_ticket_purchases` | ContractTicketPurchases | entity | extended | yes (contracts) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractTicketPurchase_get |
1823
- | `contracts` | Contract | entity | extended | no | query, count, get, create, update, resolve, search | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo, lineOfBusiness, profitability | autotask_contract_get |
1758
+ | `contracts` | Contract | entity | extended | no | query, count, get, create, update, resolve, search | fullProfile, getEntityInfo, getFieldInfo, getUdfInfo, lineOfBusiness, profitability | autotask_contract_get |
1824
1759
  | `countries` | Country | entity | advanced | no | query, count, get, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_country_get |
1825
1760
  | `currencies` | Currency | entity | advanced | no | query, count, get, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_currency_get |
1826
1761
  | `departments` | Department | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_department_get |
1827
1762
  | `domain_registrars` | DomainRegistrar | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_DomainRegistrar_get |
1828
1763
  | `expense_item_attachments` | ExpenseItemAttachment | entity | extended | yes (expense_items) | query, count, get, create, delete, resolve | download, getEntityInfo, getFieldInfo, getUdfInfo | autotask_expenseItemAttachment_get |
1829
- | `expense_items` | ExpenseItem | entity | extended | yes (expense_reports) | query, count, get, create, update, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_expenseItem_get |
1764
+ | `expense_items` | ExpenseItem | entity | extended | yes (expense_reports) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_expenseItem_get |
1830
1765
  | `expense_reports` | ExpenseReport | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_expenseReport_get |
1831
- | `holiday_sets` | HolidaySet | entity | advanced | no | query, count, get, create, update, delete, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_holidaySet_get |
1832
- | `holidays` | Holiday | entity | advanced | yes (holiday_sets) | query, count, get, create, update, delete, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_holiday_get |
1766
+ | `holiday_sets` | HolidaySet | entity | advanced | no | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_holidaySet_get |
1767
+ | `holidays` | Holiday | entity | advanced | yes (holiday_sets) | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_holiday_get |
1833
1768
  | `invoice_templates` | InvoiceTemplate | entity | advanced | no | query, count, get | getEntityInfo, getFieldInfo, getUdfInfo | autotask_invoiceTemplate_get |
1834
- | `invoices` | Invoice | entity | extended | no | query, count, get, update, resolve | customRange, getEntityInfo, getFieldInfo, getUdfInfo, last14, last24h, last30, last7, last90, lastFullFortnight, lastFullMonth, lastFullQuarter, lastFullWeek, markupHtml, markupXml, pdf, today, yesterday | autotask_invoice_get |
1769
+ | `invoices` | Invoice | entity | extended | no | query, count, get, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo, markupHtml, markupXml, pdf | autotask_invoice_get |
1835
1770
  | `notification_history` | NotificationHistory | entity | advanced | no | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_notificationHistory_get |
1836
- | `opportunities` | Opportunity | entity | extended | no | query, count, get, create, update, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_opportunity_get |
1771
+ | `opportunities` | Opportunity | entity | extended | no | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_opportunity_get |
1837
1772
  | `opportunity_attachments` | OpportunityAttachment | entity | extended | yes (opportunities) | query, count, get, create, delete, resolve | download, getEntityInfo, getFieldInfo, getUdfInfo | autotask_opportunityAttachment_get |
1838
1773
  | `opportunity_categories` | OpportunityCategory | entity | extended | no | query, count, get, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_opportunityCategories_get |
1839
1774
  | `organizational_level1s` | OrganizationalLevel1 | entity | advanced | no | query, count, get, create, update | getEntityInfo, getFieldInfo, getUdfInfo | autotask_organizationalLevel1_get |
@@ -1841,7 +1776,7 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1841
1776
  | `organizational_level_associations` | OrganizationalLevelAssociation | entity | advanced | no | query, count, get, create, update | getEntityInfo, getFieldInfo, getUdfInfo | autotask_organizationalLevelAssociation_get |
1842
1777
  | `product_vendors` | ProductVendors | entity | extended | yes (products) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_productVendor_get |
1843
1778
  | `products` | Product | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_product_get |
1844
- | `project_charges` | ProjectCharge | entity | extended | yes (projects) | query, count, get, create, update, delete, resolve, search | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_projectCharge_get |
1779
+ | `project_charges` | ProjectCharge | entity | extended | yes (projects) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_projectCharge_get |
1845
1780
  | `project_notes` | ProjectNote | entity | extended | yes (projects) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_projectNote_get |
1846
1781
  | `phases` | Phase | entity | extended | yes (projects) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_phase_get |
1847
1782
  | `tasks` | Task | entity | extended | yes (projects) | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_task_get |
@@ -1853,21 +1788,21 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1853
1788
  | `quote_items` | QuoteItem | entity | extended | yes (quotes) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_quoteItem_get |
1854
1789
  | `quote_locations` | QuoteLocation | entity | extended | yes (quotes) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_quoteLocation_get |
1855
1790
  | `quote_templates` | QuoteTemplate | entity | extended | no | query, count, get, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_quoteTemplate_get |
1856
- | `quotes` | Quote | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_quote_get |
1791
+ | `quotes` | Quote | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo, withItems | autotask_quote_get |
1857
1792
  | `resource_role_queues` | ResourceRoleQueue | entity | extended | yes (resources) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_resourceRoleQueue_get |
1858
1793
  | `resource_roles` | ResourceRole | entity | extended | yes (resources) | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_resourceRole_get |
1859
1794
  | `resource_skills` | ResourceSkill | entity | extended | yes (resources) | query, count, get, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_resourceSkill_get |
1860
1795
  | `resource_time_off_additional` | ResourceTimeOffAdditional | entity | extended | yes (resources) | update, resolve | getByResource, getEntityInfo, getFieldInfo, getUdfInfo | autotask_resourceTimeOffAdditional |
1861
1796
  | `resource_time_off_approvers` | ResourceTimeOffApprover | entity | extended | yes (resources) | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_resourceTimeOffApprover_get |
1862
1797
  | `resource_time_off_balances` | ResourceTimeOffBalance | entity | extended | yes (resources) | get, resolve | getByResource, getByYear, getEntityInfo, getFieldInfo, getUdfInfo | autotask_resourceTimeOffBalance_get |
1863
- | `resources` | Resource | entity | core | no | query, count, get, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo, transferOwnership, whoAmI | autotask_resource_get |
1798
+ | `resources` | Resource | entity | core | no | query, count, get, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo, whoAmI, workload | autotask_resource_get |
1864
1799
  | `roles` | Role | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_role_get |
1865
1800
  | `sales_orders` | SalesOrder | entity | extended | no | query, count, get, update | getEntityInfo, getFieldInfo, getUdfInfo | autotask_salesOrder_get |
1866
1801
  | `service_call_task_resources` | ServiceCallTaskResource | entity | extended | yes (service_call_tasks) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceCallTaskResource_get |
1867
1802
  | `service_call_tasks` | ServiceCallTask | entity | extended | yes (service_calls) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceCallTask_get |
1868
1803
  | `service_call_ticket_resources` | ServiceCallTicketResource | entity | extended | yes (service_call_tickets) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceCallTicketResource_get |
1869
1804
  | `service_call_tickets` | ServiceCallTicket | entity | extended | yes (service_calls) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceCallTicket_get |
1870
- | `service_calls` | ServiceCall | entity | extended | no | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceCall_get |
1805
+ | `service_calls` | ServiceCall | entity | extended | no | query, count, get, create, update, delete, resolve | fullProfile, getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceCall_get |
1871
1806
  | `service_level_agreement_results` | ServiceLevelAgreementResults | entity | advanced | no | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_serviceLevelAgreementResults_get |
1872
1807
  | `services` | Service | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_service_get |
1873
1808
  | `skills` | Skill | entity | extended | no | query, count, get, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_skill_get |
@@ -1880,13 +1815,13 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1880
1815
  | `tags` | Tag | entity | advanced | no | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_tag_get |
1881
1816
  | `task_secondary_resources` | TaskSecondaryResource | entity | extended | yes (tasks) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_taskSecondaryResources_get |
1882
1817
  | `tax_regions` | TaxRegion | entity | advanced | no | query, count, get, create, update | getEntityInfo, getFieldInfo, getUdfInfo | autotask_taxRegion_get |
1883
- | `ticket_additional_configuration_items` | TicketAdditionalConfigurationItem | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketAdditionalConfigurationItem_get |
1884
- | `ticket_additional_contacts` | TicketAdditionalContact | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketAdditionalContact_get |
1818
+ | `ticket_additional_configuration_items` | TicketAdditionalConfigurationItem | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketAdditionalConfigurationItem_get |
1819
+ | `ticket_additional_contacts` | TicketAdditionalContact | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketAdditionalContact_get |
1885
1820
  | `ticket_attachments` | TicketAttachment | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | download, getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketAttachment_get |
1886
1821
  | `ticket_categories` | TicketCategory | entity | extended | no | query, count, get, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketCategories_get |
1887
1822
  | `ticket_category_field_defaults` | TicketCategoryFieldDefault | entity | extended | yes (ticket_categories) | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketCategoryFieldDefaults_get |
1888
1823
  | `ticket_change_request_approvals` | TicketChangeRequestApproval | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketChangeRequestApprovals_get |
1889
- | `ticket_charges` | TicketCharge | entity | extended | yes (tickets) | query, count, get, create, update, delete, resolve, search | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo, moveToTicket | autotask_ticketCharge_get |
1824
+ | `ticket_charges` | TicketCharge | entity | extended | yes (tickets) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo, moveToTicket | autotask_ticketCharge_get |
1890
1825
  | `ticket_checklist_items` | TicketChecklistItem | entity | extended | yes (tickets) | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketChecklistItem_get |
1891
1826
  | `ticket_checklist_libraries` | TicketChecklistLibrary | entity | extended | yes (tickets) | create, resolve | attachToDocument, getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketChecklistLibrary |
1892
1827
  | `ticket_history` | TicketHistory | entity | extended | no | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketHistory_get |
@@ -1895,8 +1830,8 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1895
1830
  | `ticket_notes` | TicketNote | entity | extended | yes (tickets) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketNote_get |
1896
1831
  | `ticket_secondary_resources` | TicketSecondaryResource | entity | extended | yes (tickets) | query, count, get, create, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketSecondaryResources_get |
1897
1832
  | `ticket_webhooks` | TicketWebhook | entity | extended | yes (tickets) | query, count, get, create, update, delete, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_ticketWebhook_get |
1898
- | `tickets` | Ticket | entity | core | no | query, count, get, create, update, resolve, search, getContext | changeStatus, findByTicketNumber, forResource, fullContext, getEntityInfo, getFieldInfo, getUdfInfo, lineOfBusiness, openWithNoteAndAssign, slaHealthCheck, statusSemantics, summary, timeline | autotask_ticket_get |
1899
- | `time_entries` | TimeEntry | entity | extended | no | query, count, get, create, update, delete, resolve | approve, createForResource, createIfNotExists, customRange, dateWorkedWindow, getEntityInfo, getFieldInfo, getPosted, getUdfInfo, getUnposted, last14, last24h, last30, last7, last90, lastFullFortnight, lastFullMonth, lastFullQuarter, lastFullWeek, moveToTicket, postedWithin, today, yesterday | autotask_timeEntry_get |
1833
+ | `tickets` | Ticket | entity | core | no | query, count, get, create, update, resolve, search, getContext | changeStatus, findByTicketNumber, forResource, fullContext, getEntityInfo, getFieldInfo, getUdfInfo, lineOfBusiness, slaHealthCheck, statusSemantics, summary, timeline | autotask_ticket_get |
1834
+ | `time_entries` | TimeEntry | entity | extended | no | query, count, get, create, update, delete, resolve | dateWorkedWindow, getEntityInfo, getFieldInfo, getUdfInfo, moveToTicket | autotask_timeEntry_get |
1900
1835
  | `time_entry_attachments` | TimeEntryAttachment | entity | extended | yes (time_entries) | query, count, get, create, delete, resolve | download, getEntityInfo, getFieldInfo, getUdfInfo | autotask_timeEntryAttachment_get |
1901
1836
  | `time_off_requests` | TimeOffRequest | entity | extended | no | query, count, get, resolve | approve, getEntityInfo, getFieldInfo, getUdfInfo, reject | autotask_timeOffRequest_get |
1902
1837
  | `inventory_products` | InventoryProduct | entity | extended | no | query, count, get, create, update, delete, resolve | getEntityInfo, getFieldInfo, getUdfInfo, stockedItemsAdd, stockedItemsRemove, stockedItemsTransfer | autotask_inventoryProduct_get |
@@ -1908,7 +1843,7 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
1908
1843
  | `threshold_information` | — | integration | core | no | get | — | autotask_threshold_information |
1909
1844
  | `zone_information` | — | integration | advanced | no | get | — | autotask_zone_information |
1910
1845
 
1911
- 128 resources (121 entity + 7 integration). Rows are generated from the registry — do not hand-type.
1846
+ 136 resources (129 entity + 7 integration). Rows are generated from the registry — do not hand-type.
1912
1847
  <!-- END GENERATED:api-per-entity -->
1913
1848
 
1914
1849
  **`companies.search` include_family (issue #59).** `companies.search` is
@@ -1972,15 +1907,14 @@ drift). Records exist only for plan rows that are implemented/tested; the
1972
1907
  plan (`capabilities.plan.json`) is the single source of truth and the
1973
1908
  registry is its compiled form.
1974
1909
 
1975
- **Consumer-defaulted arguments (issue #115).** A manifest CONSUMER may fill an
1976
- argument this registry marks required when the caller omits it — the manifest
1977
- describes the SDK operation, not the consumer's tool contract. The one
1978
- instance today is `contracts.profitability`: the SDK requires
1979
- `dateFrom`/`dateTo`, and iit-mcp-autotask defaults both to the last full
1980
- calendar month (UTC) when the caller omits them. The served
1910
+ **SDK-default window (issues #115/#291).** `contracts.profitability` takes
1911
+ `dateFrom`/`dateTo` as an optional PAIR: both omitted → the window defaults to
1912
+ the last full calendar month (UTC) and the result's `period` carries
1913
+ `defaulted: true` plus a note (month label + UTC basis); exactly one supplied
1914
+ → a typed `CONFIG_ERROR` before any wire; both supplied → the existing
1915
+ validation. The consumer no longer fills the window — the served
1981
1916
  `metadata.args`/`metadata.usage` and the record's
1982
- `inputSchema.opts.description` state the caveat; a consumer that trusts the
1983
- raw manifest should apply the same contract.
1917
+ `inputSchema.opts.description` state the SDK default.
1984
1918
 
1985
1919
  ### 9.1 Generated artifact #5: `src/relationships.ts` (the relationship map + zone facts)
1986
1920
 
@@ -2005,11 +1939,11 @@ cannot drift from the plan):
2005
1939
  describe channel cannot drift.
2006
1940
  - `DELETE_BOUNDARY_NOTES` (issue #9) — resource → the spec-derived
2007
1941
  delete-boundary note (`plan.deleteBoundaries`): the create dry run's
2008
- `delete-boundary` warning and `operations.recovery` read the SAME map (the
2009
- describe and runtime channels cannot drift).
2010
- - `MARKER_FIELDS` (issue #9) — resource → the spec-derived name-like field
2011
- the recovery marker query filters on (the plan's `markerField`; absent when
2012
- the model has none).
1942
+ `delete-boundary` warning reads the map (the op that shared it,
1943
+ `operations.recovery`, was deleted in #289 — the dry-run and describe
1944
+ channels cannot drift).
1945
+ - `MARKER_FIELDS` (issue #9) — resource → the spec-derived name-like marker
1946
+ field (the plan's `markerField`; absent when the model has none).
2013
1947
  - `EMPTY_REASON_STAMPS` (issue #14) — resource → op → the typed empty-reason
2014
1948
  `{ surface, kind, note, authority, remedy }` (`plan.emptyReasons`, the
2015
1949
  ww29-sweep-pinned 184 rows): the runtime lookup `base.ts` attaches to
@@ -2031,10 +1965,10 @@ The per-resource entry also carries `deleteBoundary`
2031
1965
 
2032
1966
  Consumers: the runtime sentinel wrap (T10), `operations.relationshipMap`
2033
1967
  (S12-B — the `autotask_relationship_map` tool serves `RELATIONSHIP_MAP`
2034
- zero-wire by construction), the create dry run (§7.5) and
2035
- `operations.recovery` (issue #9). Drift-guarded: `capabilities:check` fails
2036
- when the artifact bytes ≠ a fresh re-derivation of the plan
2037
- (`test/scripts/relationships-artifact.test.ts` pins it TDD-style).
1968
+ zero-wire by construction) and the create dry run (§7.5). Drift-guarded:
1969
+ `capabilities:check` fails when the artifact bytes ≠ a fresh re-derivation
1970
+ of the plan (`test/scripts/relationships-artifact.test.ts` pins it
1971
+ TDD-style).
2038
1972
  ## 10. Response redaction (secret and personal fields)
2039
1973
 
2040
1974
  The SDK boundary redacts response field values. A vendor 2xx JSON body is
@@ -2119,8 +2053,9 @@ resource paths, of which **40** expose a create op (the stampable set; the
2119
2053
  13-record live-verified set above stays a distinct channel, `proof:
2120
2054
  'live-verified'`). The new `plan.deleteBoundaries` block (spec-derived,
2121
2055
  `proof: 'spec-derived'`) stamps each affected create's descriptor
2122
- (`metadata.deleteBoundary`), the create dry run (§7.5) and the one-call
2123
- `recovery` op read the SAME generated note — see §11.9.
2056
+ (`metadata.deleteBoundary`) and the create dry run (§7.5) read the SAME
2057
+ generated note (the one-call recovery op that shared it was deleted in
2058
+ #289) — see §11.9.
2124
2059
 
2125
2060
  ### 11.2 Blind count/query surfaces (get is the authority)
2126
2061
 
@@ -2166,7 +2101,7 @@ resource paths, of which **40** expose a create op (the stampable set; the
2166
2101
  type itself is discoverable via `configuration_item_types.query` (the wall is the
2167
2102
  FK, not the type).
2168
2103
 
2169
- ### 11.4 `time_entries.createForResource` — zone facts
2104
+ ### 11.4 `time_entries.create` — zone facts
2170
2105
 
2171
2106
  - The vendor **double-validates roleID** (an active general allocation code AND
2172
2107
  a Role reference); in ww29 the two sets are disjoint, so the task/ticket
@@ -2299,13 +2234,12 @@ of the data).
2299
2234
  everything — the sentinel notes, the record stamps, the generated
2300
2235
  `src/relationships.ts` (§9.1) and this section.
2301
2236
  - **S12 additions (PLANNED — land with the A/B workstreams, not yet on
2302
- main)**: 9 new registry ops — `tickets.openWithNoteAndAssign` /
2237
+ main)**: new registry ops — `tickets.openWithNoteAndAssign` /
2303
2238
  `tickets.changeStatus` / `tickets.fullContext` · `contacts.upsertByEmail` ·
2304
2239
  `companies.upsertByName` / `companies.fullProfile` ·
2305
- `configuration_items.profile` · `operations.fetchMany` (batch read) /
2306
- `operations.relationshipMap` (serves the §9.1 artifact zero-wire). Each
2307
- gets a curation row in `MCP_TOOL_OVERRIDES.json` at the post-merge
2308
- coordinator step.
2240
+ `configuration_items.profile` · `operations.relationshipMap` (serves the
2241
+ §9.1 artifact zero-wire). Each gets a curation row in
2242
+ `MCP_TOOL_OVERRIDES.json` at the post-merge coordinator step.
2309
2243
 
2310
2244
  ### 11.8 Live-validated create examples + the `createIfNotExists` dedupFields shapes
2311
2245
 
@@ -2404,11 +2338,13 @@ treat `entry.evidence` with the same trust you already apply to
2404
2338
  `vendorEvidence` on this surface (plan-grade provenance, not model-facing
2405
2339
  prose). The old `string[]` shape is gone.
2406
2340
 
2407
- ### 11.9 Delete-boundary awareness + the one-call recovery (issue #9)
2341
+ ### 11.9 Delete-boundary awareness (issue #9)
2408
2342
 
2409
2343
  The §11.1 wall (7 live-verified 405 endpoints) is now a **spec-derived
2410
2344
  channel** that covers the whole surface, not just the 13 live-verified
2411
- records. The audit (`scripts/lib/delete-boundary.mjs` — the same shared lib
2345
+ records. The one-call recovery op that turned this boundary into a typed,
2346
+ bounded path was deleted in #289 (no server consumer) — the channel below
2347
+ is the plan data + the create dry run that remain. The audit (`scripts/lib/delete-boundary.mjs` — the same shared lib
2412
2348
  the S11-F stamp channel and the F2 manifest note use) re-derives the
2413
2349
  vendored spec's DELETE-absence set, and the plan carries it as a NEW top-level
2414
2350
  block (distinct from `plan.blindSurfaces`, which the 6-pin set-equality gate
@@ -2424,27 +2360,25 @@ keeps untouched):
2424
2360
  `autotask_describe` surfaces it before the write).
2425
2361
  - **generated runtime data** — `src/relationships.ts` emits
2426
2362
  `DELETE_BOUNDARY_NOTES` (resource → note) + `MARKER_FIELDS` (resource → the
2427
- spec-derived name-like field the recovery marker query filters on, 22 pins)
2428
- from the same plan (zero wire by construction).
2363
+ spec-derived name-like marker field, 22 pins) from the same plan (zero
2364
+ wire by construction).
2429
2365
  - **the create dry run** (`src/resources/base.ts`) — on a delete-less
2430
2366
  resource the envelope gains the report-only `delete-boundary` check
2431
2367
  (`ok: true`) + the boundary note in `warnings[]`. The check is
2432
2368
  report-only on purpose: the write itself is valid, so `wouldApply` is
2433
2369
  preserved (the design's `ok: false` example would have broken the pinned
2434
2370
  `companies`/`tickets` create-dry-run contract).
2435
- - **`operations.recovery`** (§7.5) — the one-call recovery/cleanup op that
2436
- turns the boundary from a wall into a typed, bounded path: find the record
2437
- a write just created (one bounded marker query), and where the vendor
2438
- allows it, remove it (dry-run-first, confirm-gated apply, per-id
2439
- verification). The three new typed errors (`DELETE_BOUNDARY`,
2371
+ - **typed errors** — the three issue-#9 refusal codes (`DELETE_BOUNDARY`,
2440
2372
  `MARKER_UNSAFE_ID`, `RECOVERY_TRUNCATED`) + the reused `RESOURCE_UNKNOWN`
2441
- make every refusal explicit (never a raw 405, never a silent skip, never a
2442
- silent empty).
2443
- - **the gate** (`scripts/check-capabilities.mjs`) — the new channel has its
2444
- own set-equality clause: the block must equal a fresh spec re-derivation
2373
+ stay in the write-outcome taxonomy (`src/errors/mutation.ts`) after #289
2374
+ deleted the op that threw them: never a raw 405, never a silent skip,
2375
+ never a silent empty.
2376
+ - **the gate** (`scripts/check-capabilities.mjs`) — the channel has its own
2377
+ set-equality clause: the block must equal a fresh spec re-derivation
2445
2378
  over the stampable set, and EXACTLY the pinned create records carry
2446
2379
  `metadata.deleteBoundary` (content match). Pinned by
2447
- `test/operations/recovery.test.ts` + the full-surface governor suite.
2380
+ `test/scripts/check-capabilities.test.ts` (the F5 clauses) + the
2381
+ full-surface governor suite.
2448
2382
 
2449
2383
  ### 11.10 Typed empty reasons — `emptyReason` (issue #14)
2450
2384
 
@@ -2525,14 +2459,17 @@ blind) return `{ count, emptyReason? }` on a 0 — check
2525
2459
  existing contract, now with the reason alongside).
2526
2460
 
2527
2461
 
2528
- ### 11.11 Zero-wire planning reads + the bounded reachability probe (issue #11)
2462
+ ### 11.11 The zero-wire blind-surface + reachability plan data (issue #11)
2529
2463
 
2530
- Two ops serve the campaign's own discovery data at runtime (ZERO vendor
2531
- calls — generated plan data, drift-guarded by `capabilities:check` clause
2532
- 14), so an agent can read "what is blind on this zone" without burning
2533
- budget or guessing:
2464
+ The campaign's own discovery data ships as plan data (ZERO vendor calls —
2465
+ `plan.reachability` is derived into `src/relationships.ts`, drift-guarded
2466
+ by `capabilities:check` clause 14), so an agent can read "what is blind on
2467
+ this zone" without burning budget or guessing. The two ops that served it
2468
+ at runtime (`operations.blindSurfaces`, `operations.probeReachability`)
2469
+ were deleted in #289 (no server consumer) — the data and its derivation
2470
+ remain:
2534
2471
 
2535
- - **`operations.blindSurfaces`** — the 124-surface inventory: per surface the
2472
+ - **`BLIND_SURFACE_INVENTORY`** — the 124-surface inventory: per surface the
2536
2473
  kind (`unowned-blind` / `unfiltered-blind` / `unknown` — the 46
2537
2474
  data-with-rows surfaces ride `unknown` by design: the sweep's limit-2 read
2538
2475
  proves data, not the absence of it), the recorded `unfilteredRows` (0-2
@@ -2543,14 +2480,13 @@ budget or guessing:
2543
2480
  note. Clause 14 pins the row set equal to the plan's query-able resources,
2544
2481
  the counts derived from the rows, and the row-level kind invariant (a
2545
2482
  blind-class row must have 0 recorded rows).
2546
- - **`operations.probeReachability`** — re-probes chosen surfaces LIVE
2547
- (effect=read: route it through `autotask_read`): `{ resources?, maxWireCalls?,
2548
- confirmProbe }` — `confirmProbe: true` is REQUIRED (the probe issues real
2549
- vendor reads), caps of 40 surfaces / 80 wire calls with OVER-CAP REFUSAL
2550
- (never truncation), per-surface failure isolation (a 500 on one surface —
2551
- e.g. the W1 `ticket_history` contract 500 on the unfiltered read — is logged
2552
- with its machine code and the sweep continues), `wireUsed` single-sourced, zero results honest (a 0
2553
- from a blind surface proves nothing — the inventory's kind says why).
2483
+ - **`REACHABILITY_PROBE_DEFAULTS`** — the derived probe defaults: the hard
2484
+ wire cap (80), the default cap (40), the limit bounds (1-2, default 2)
2485
+ and the default target set — the derived non-data-full rows (the 70
2486
+ surfaces a re-discovery sweep starts from: the 69 blind +
2487
+ `ticket_history`). Derived zero-wire; the gate re-derives the target set,
2488
+ so it cannot drift from the inventory. (A 0 from a blind surface proves
2489
+ nothing — the inventory's kind says why.)
2554
2490
 
2555
2491
  ### 11.12 Zone-fact create guidance (issue #12)
2556
2492
 
@@ -2697,7 +2633,7 @@ metadata is (the `autotask_describe` channel, `capabilities.json`,
2697
2633
  key. Unlike `pointerNote`, caveats ride the describe channel only (they are
2698
2634
  not attached to error details).
2699
2635
 
2700
- ### 11.13 Posted state from billing items (W-D3), `getPosted` / `getUnposted` / `postedWithin`, and drifted-create marker recovery (issues #10/#13/#237)
2636
+ ### 11.13 Posted state from billing items (W-D3), `dateWorkedWindow`, and drifted-create marker recovery (issues #10/#13/#237)
2701
2637
 
2702
2638
  - **W-D3 (live, ww29, re-verified 2026-10-27)** — this zone's TimeEntry entity
2703
2639
  has **no posted-state field**: time-entry reads carry no `postedDate` /
@@ -2717,52 +2653,39 @@ metadata is (the `autotask_describe` channel, `capabilities.json`,
2717
2653
  condition is ever sent. The join is one `BillingItems` query per 250 ids;
2718
2654
  the entry read is capped at 500, so a call costs at most 1 read + 2 joins
2719
2655
  and a window with more than 500 entries returns the first 500 with
2720
- `hasMore` / `truncated` true and a `nextCursor` to continue from (or use
2656
+ `truncated` true and a `nextCursor` to continue from (or use
2721
2657
  `countOnly` for the window's totals; see the paging bullet below). A 429 or vendor fault propagates as a typed
2722
2658
  error. The API user therefore needs BillingItems read permission; a permission fault propagates as a typed error.
2723
- - **`time_entries.getPosted(opts?)` / `getUnposted(opts?)`** — `opts` is
2724
- `{ limit?, ticketID?, dateFrom?, dateTo?, cursor?, countOnly?, maxVendorCalls? }`
2725
- (the paging options: see the paging bullet below; `dateFrom`/`dateTo`:
2726
- inclusive `dateWorked` days, `YYYY-MM-DD`, never tz-shifted). They return
2727
- `{ items, hasMore, window, nextCursor, unconfirmed, approvedCount, unapprovedCount }` (a breaking
2728
- change from the bare `TimeEntry[]`, 0.32.0): `getPosted` items are entries
2729
- a billing item references; `getUnposted` items are entries none references.
2730
- `approvedCount` / `unapprovedCount` are informational (items with / without
2731
- `billingApprovalDateTime`) and are **not** posted state. `window` echoes the
2732
- bounds (`null` for a bound not given); `hasMore` true means the result is
2733
- partial (entry read capped, or a join page truncated — unconfirmed entries
2734
- are withheld, never called unposted). `dateFrom`/`dateTo` are optional, but a call without dates reads the tenant's first 500 time entries (effectively the oldest): a near-empty, misleading slice with `hasMore` true — pass a window.
2735
- - **`time_entries.postedWithin(from, to, opts?)`** — an inclusive UTC
2736
- `dateWorked` window (`from`/`to` are `YYYY-MM-DD` date strings — the S1c
2737
- guard forbids Date positionals), one bounded read (`limit` 1-500, default
2738
- 500 — the hard cap, issue #128: a larger window needs `time_entries.query`
2739
- with the same `dateWorked` filter and a suitable `limit` or explicit
2740
- `walk.maxRows` cap, section 4) returning
2741
- `{ items, postedCount, unpostedCount, window, nextCursor, unconfirmed, truncated }`; the counts come
2742
- from the billing-item join (any billing item referencing the entry), `opts.posted` narrows client-side after the join
2743
- (true = posted only, false = unposted only, omitted = both); the counts still split every entry read (before the narrowing), so `items.length` can differ from their sum and `ticketID` /
2744
- `resourceID` narrow the read. Live evidence for the visibility class
2745
- (unfiltered query 0 rows, windowed read has data) from 2026-10-06 still
2746
- stands; the posted/unposted split from that period does not.
2747
- - **`truncated` + the `dateWorkedWindow` alias + `tz` (issue #66)** — the
2748
- result is now `{ items, postedCount, unpostedCount, window, truncated }`:
2749
- `truncated` is true when the bounded read came back with a live
2750
- `pageDetails.nextPageUrl` (more matching rows exist; `requestCount` is the
2751
- MaxRecords echo, not a total, so it cannot carry this signal) —
2752
- the posted/unposted counts then cover the returned rows ONLY (at most
2753
- `limit`); re-query with a wider window or a narrowing filter to reconcile
2754
- the totals. `time_entries.dateWorkedWindow(from, to, opts?)` is the
2755
- unambiguous-named alias: the same wire, the same result (incl. `truncated`),
2756
- its own op identity (plan row / MCP tool
2757
- `autotask_timeEntry_date_worked_window` / operation label) — the name
2758
- names the `dateWorked` field the window is on; `postedWithin` keeps its
2759
- name and default semantics. Both accept `opts.tz` (an IANA zone, validated)
2760
- but, since 0.24.0, it never shifts the bounds: `dateWorked` is a date-only
2761
- value at 00:00 UTC, so `from`/`to` are the days (see §7.11). The same `tz`
2762
- opt is on every date-window op — see §11.22.
2763
- - **Paging past 500 entries and whole-window totals (issue #242)** — all four
2764
- ops (`getPosted`, `getUnposted`, `postedWithin`, `dateWorkedWindow`) take
2765
- three more options:
2659
+ - **`time_entries.dateWorkedWindow(from, to, opts?)`** — the ONE posted-state read
2660
+ (issue #290; absorbs `getPosted` / `getUnposted` / `postedWithin` and the
2661
+ relative windows). `from`/`to` are required inclusive UTC `dateWorked` days
2662
+ (`YYYY-MM-DD` date strings — the guard forbids Date positionals and refuses
2663
+ impossible days), one bounded read (`limit` 1-500, default 500 — the hard cap,
2664
+ issue #128: a larger window needs `time_entries.query` with the same
2665
+ `dateWorked` filter and a suitable `limit` or explicit `walk.maxRows` cap,
2666
+ section 4) returning
2667
+ `{ items, postedCount, unpostedCount, window, nextCursor, unconfirmed, truncated }`;
2668
+ the counts come from the billing-item join (any billing item referencing the
2669
+ entry) and split every entry read (before the narrowing), so `items.length`
2670
+ can differ from their sum; `opts.state` (`'posted'` / `'unposted'`, omitted =
2671
+ both) narrows `items` client-side after the join, and `ticketID` / `resourceID`
2672
+ narrow the read. The no-window form (a call without dates reading the
2673
+ tenant's first 500 time entries, effectively the oldest) is gone: a read with
2674
+ no `dateWorked` filter goes through `time_entries.query`. Live evidence for
2675
+ the visibility class (unfiltered query 0 rows, windowed read has data) from
2676
+ 2026-10-06 still stands; the posted/unposted split from that period does not.
2677
+ - **`truncated` (issue #66, consolidated in #290)** — `truncated` is true when
2678
+ the bounded read came back with a live `pageDetails.nextPageUrl` (more
2679
+ matching rows exist; `requestCount` is the MaxRecords echo, not a total, so
2680
+ it cannot carry this signal) or a join page was truncated — the
2681
+ posted/unposted counts then cover the returned rows ONLY (at most `limit`);
2682
+ re-query with a wider window or a narrowing filter to reconcile the totals.
2683
+ The `tz` option is gone with the consolidation (`dateWorked` is a date-only
2684
+ value at 00:00 UTC — the `from`/`to` days ARE the days, never shifted; see
2685
+ §7.11), and the `postedWithin` / `dateWorkedWindow` alias pair is one op now
2686
+ (plan row / MCP tool `autotask_timeEntry_date_worked_window`).
2687
+ - **Paging past 500 entries and whole-window totals (issue #242)** —
2688
+ `dateWorkedWindow` takes three more options:
2766
2689
  - `cursor?: string` — every rows result now carries `nextCursor: string | null`
2767
2690
  (the vendor's next-page cursor for the entry read). Pass it back as
2768
2691
  `opts.cursor` with the **same** window and filters to read the next page;
@@ -2773,12 +2696,11 @@ metadata is (the `autotask_describe` channel, `capabilities.json`,
2773
2696
  continuation (`/V1.0/TimeEntries/query/next`), or a cursor longer than
2774
2697
  2,048 characters, is refused (`RESPONSE_REJECTED`) before any request. Every rows result also carries
2775
2698
  `unconfirmed: number`: the entries on this page whose billing-item join
2776
- page was truncated. They are withheld from `items` (getPosted /
2777
- getUnposted) and from both counts (postedWithin / dateWorkedWindow, which
2778
- also withhold them from `items` when `posted` is set; with `posted`
2779
- omitted they stay in `items`, unclassified), so a cursor loop does not
2780
- see them as posted or unposted; `unconfirmed` is 0 normally, and when it is
2781
- above 0 `hasMore` / `truncated` is also true. Sum it across a loop to know
2699
+ page was truncated. They are withheld from a `state`-narrowed `items`
2700
+ (with `state` omitted they stay in `items`, unclassified) and from both
2701
+ counts, so a cursor loop does not see them as posted or unposted;
2702
+ `unconfirmed` is 0 normally, and when it is above 0 `truncated` is also
2703
+ true. Sum it across a loop to know
2782
2704
  how many entries went unclassified (or use `countOnly`, which refuses
2783
2705
  instead).
2784
2706
  - `countOnly?: true` — returns only the whole window's totals:
@@ -2794,7 +2716,7 @@ metadata is (the `autotask_describe` channel, `capabilities.json`,
2794
2716
  and dropped another); a total equal to either count is accepted, so a
2795
2717
  window that grows or shrinks by the time the walk ends still succeeds, and
2796
2718
  one that changes mid-walk can fail or pass depending on timing: retry.
2797
- `limit` and `posted` do not apply; `cursor` is rejected with `countOnly`
2719
+ `limit` and `state` do not apply; `cursor` is rejected with `countOnly`
2798
2720
  (CONFIG_ERROR).
2799
2721
  - `maxVendorCalls?: number` — the `countOnly` budget (integer 1-200,
2800
2722
  default 30). The estimate is `1 + max(1, ceil(n / 500)) + ceil(n / 250) + 1`
@@ -2880,7 +2802,7 @@ resources, `include: ['fields']`; the closed opts bag is documented in
2880
2802
  the registry record). One bounded entityInformation GET per resource ×
2881
2803
  kind; the wire cost `resources × include` is REFUSED pre-request above
2882
2804
  the pinned cap (`MAX_FIELD_CATALOG_WIRE = 60`) — refused, never
2883
- truncated (the `probeReachability` contract). Per-resource vendor
2805
+ truncated (the bounded sweep contract). Per-resource vendor
2884
2806
  failures are recorded on the row (`error: { code, message }`) and never
2885
2807
  abort the sweep. Capability is PLAN-DERIVED, never hardcoded: the
2886
2808
  `getFieldInfo` / `getUdfInfo` row sets and each resource's vendor path
@@ -3393,58 +3315,35 @@ call.
3393
3315
  Pins: `test/operations/expandResourceNames.test.ts` (the plan row's `tests[]`)
3394
3316
  and `test/resources/cache-write-invalidation.test.ts` (mock wire only).
3395
3317
 
3396
- ### 11.22 Timezone-anchored date windows for billing items and invoices (issue #66)
3397
-
3398
- The time-entry window helpers pinned their bounds to UTC (the API is UTC)
3399
- while the node computes them in the configured timezone, and billing items /
3400
- invoices had no window helpers at all. Issue #66 makes the zone EXPLICIT and
3401
- additive, and mirrors the family:
3402
-
3403
- - **Two new window families** — the full mirror of the time-entry helpers
3404
- (the same 12 ops each: `today`, `yesterday`, `last24h`, `last7`, `last14`,
3405
- `last30`, `last90`, `lastFullWeek`, `lastFullFortnight`, `lastFullMonth`,
3406
- `lastFullQuarter`, `customRange`):
3407
- - `billing_items.<kind>(opts?)` — ONE bounded server-filtered query on
3408
- the billing item's date field **`itemDate`**
3409
- (`POST /V1.0/BillingItems/query`) → `Promise<BillingItems[]>`;
3410
- - `invoices.<kind>(opts?)` — ONE bounded server-filtered query on the
3411
- invoice's date-time field **`invoiceDateTime`**
3412
- (`POST /V1.0/Invoices/query`) → `Promise<Invoice[]>` (the invoices
3413
- model has no plain `invoiceDate` — `fromDate`/`toDate` are range
3414
- fields, not the invoice date).
3415
- - **The zone contract (shared)** — `opts.tz` takes an explicit IANA zone
3416
- (e.g. `'Australia/Sydney'`); OMITTED `tz` = UTC, the current behaviour
3417
- bit-for-bit (the regression pin). All three families are DATE-ONLY
3418
- (`dateWorked`, `itemDate`, `invoiceDateTime` are stored at T00:00:00Z): the
3419
- zone picks which calendar DATES a relative window means (what today / last
3420
- full month is in that zone) and the wire bounds are whole UTC days of them
3421
- (second-precision UTC ISO); explicit `from`/`to` days are never shifted and
3422
- `tz` is only validated. `time_entries` has worked this way since 0.24.0;
3423
- `billing_items` and `invoices` since 0.32.0 (behaviour change: before, a zone
3424
- behind UTC, e.g. America/New_York, sent a zone-converted instant window that
3425
- dropped the first day and added an extra day; zones ahead of UTC returned the
3426
- right rows by accident). A zone-converted instant window picks the wrong day
3427
- behind UTC. DST is
3428
- handled by inverting local midnights to UTC instants (a local day that
3429
- gains or loses an hour still pins both boundaries to its TRUE midnights —
3430
- never clock-hour arithmetic); `last24h` is rolling (`now − 24h → now`)
3431
- and TZ-invariant by construction. A zone `Intl.DateTimeFormat` rejects is
3432
- refused pre-request as a typed `CONFIG_ERROR` (zero wire). There is no
3433
- config-level timezone in v1 (the zone is per-call; a future
3434
- `config.timeZone` default would be an additive follow-up).
3435
- - **The existing family gains the same opt** — the 12 `time_entries`
3436
- window ops (incl. `customRange`) plus `postedWithin` and its
3437
- `dateWorkedWindow` alias accept `opts.tz`
3438
- with the contract above (date-only for time entries), so the whole window family accepts
3439
- the same `tz` option (default UTC unchanged).
3440
- - **Shape and wire** — each op is exactly ONE bounded query (`MaxRecords`
3441
- 1-500, default 500 — the hard cap, issue #128: a call without `limit` takes
3442
- the first 500 rows in id order; a window with more than 500 matching rows
3443
- needs the bounded query op with the same date-window filter and a suitable
3444
- `limit` or explicit `walk.maxRows` cap (section 4); the `itemDate` /
3445
- `invoiceDateTime` `gte`/`lte` filter rides the
3446
- wire), and the result is the plain `items[]` array — no counts
3447
- (the time-entry family shape).
3318
+ ### 11.22 The cut date-window families — `dateWorkedWindow` and bounded queries (issue #66, consolidated in #290)
3319
+
3320
+ Issue #66 made the window zone EXPLICIT (a `tz` option, default UTC) and
3321
+ mirrored the time-entry window family onto `billing_items` (`itemDate`) and
3322
+ `invoices` (`invoiceDateTime`) — twelve relative ops each. The consolidation
3323
+ (issue #290) cut all of them: ONLY the `time_entries` family is consolidated
3324
+ into the single op `time_entries.dateWorkedWindow(from, to, opts?)` (§11.13)
3325
+ — an inclusive, UTC-anchored `dateWorked` window with the posted/unposted
3326
+ split. The `billing_items` and `invoices` date-window families are CUT with
3327
+ no replacement op: reading those resources by date window goes through their
3328
+ bounded `query` ops on `itemDate` / `invoiceDateTime` (bullet below). The
3329
+ `tz` option is gone (`dateWorked` is a date-only value at 00:00 UTC; the
3330
+ `from`/`to` days ARE the wire bounds, never shifted, and impossible days are
3331
+ refused pre-request).
3332
+
3333
+ - **Reading billing items / invoices by date window** — use the bounded `query`
3334
+ ops: `billing_items.query` with an `itemDate` `gte`/`lte` filter on whole UTC
3335
+ days (`POST /V1.0/BillingItems/query`) and `invoices.query` with
3336
+ `invoiceDateTime` (`POST /V1.0/Invoices/query` — the invoices model has no
3337
+ plain `invoiceDate`; `fromDate`/`toDate` are range fields, not the invoice
3338
+ date). Each call is ONE bounded query (`MaxRecords` 1-500, default 500 — the
3339
+ hard cap, issue #128), the plain `items[]` result, no counts.
3340
+ - **The zone contract (history)** — before #290 the relative windows
3341
+ (`today`, `lastFullMonth`, ...) computed the calendar dates in `opts.tz` and
3342
+ sent whole UTC days (DST handled by inverting local midnights to UTC instants;
3343
+ `last24h` was rolling, `now − 24h → now`, TZ-invariant by construction); a
3344
+ zone the vendor's `Intl.DateTimeFormat` rejects was refused pre-request as a
3345
+ typed `CONFIG_ERROR` (zero wire). No relative-window op remains, so the
3346
+ contract only matters for historical receipts.
3448
3347
 
3449
3348
  ### 11.23 Free-text caps (issue #61)
3450
3349
 
@@ -3660,8 +3559,8 @@ returns the contract's reconciled P&L over a date window:
3660
3559
  (ticket `createDate`) as UTC instants, and bucketing slices the ISO
3661
3560
  string — in a UTC+10/11 tenant a ticket created late local time buckets
3662
3561
  one UTC day (possibly one month) earlier than its local date. The
3663
- `time_entries.getPosted` / `getUnposted` ops are NEVER called (the
3664
- profitability fold already holds the billing items, so the join is
3562
+ posted-state ops are gone (cut in #290 — the profitability fold
3563
+ already holds the billing items, so the join is
3665
3564
  in-memory with no extra wire); posted/unposted derives from the
3666
3565
  `billingItems.timeEntryID` join (unposted = no billing item exists yet).
3667
3566
  The structural exclusions (`/Services/`, `/ExpenseReports/`,
@@ -3742,7 +3641,7 @@ returns the contract's reconciled P&L over a date window:
3742
3641
  SERVICE lines)` (labour and charges never mask a missed recurring
3743
3642
  invoice) · `unposted_time { entries, hours, hoursAbsent?, value, absent? }`
3744
3643
  (billable time with no billing item yet — derived from the billing join,
3745
- never getPosted/getUnposted; `hours` = Σ `max(0, hoursToBill)`, or `null`
3644
+ never a posted-state op (cut in #290); `hours` = Σ `max(0, hoursToBill)`, or `null`
3746
3645
  + `hoursAbsent: 'HOURS_TO_BILL_MISSING'` when any of the month's unposted
3747
3646
  entries carries no numeric `hoursToBill` — unknown, never a summed 0; a
3748
3647
  deliberate `hoursToBill: 0` stays 0; `value` = Σ `hoursToBill ×
@@ -3925,8 +3824,8 @@ returns the contract's reconciled P&L over a date window:
3925
3824
  ### 11.27 Label siblings on query, aggregate and fetch (opt-in, issue #287)
3926
3825
 
3927
3826
  Rows served by the invoke `<resource>.query` op (the bounded query), the group
3928
- keys of `operations.aggregate`, and the found records of `operations.fetchMany`
3929
- / `operations.fetchByIds` carry label siblings **when opted in** (issue #287:
3827
+ keys of `operations.aggregate`, and the found records of `operations.fetchByIds`
3828
+ carry label siblings **when opted in** (issue #287:
3930
3829
  OFF by default — the label pass costs a field-catalog read plus up to 20 cold
3931
3830
  name reads per call, so a plain read returns raw ids), through the same
3932
3831
  `resolveCompositeNames` path the full-context composites use:
@@ -3935,9 +3834,9 @@ name reads per call, so a plain read returns raw ids), through the same
3935
3834
  siblings on for every read surface of that client (the MCP server sets it,
3936
3835
  server-side).
3937
3836
  - **The per-call key:** `names: true` in the query bag / the aggregate opts /
3938
- the `fetchMany` / `fetchByIds` opts turns them on for that call; `names:
3939
- false` turns them off (even when the client has `labels: true`); ABSENT =
3940
- the client's option (the off default). A non-boolean `names` is refused
3837
+ the `fetchByIds` opts turns them on for that call; `names: false` turns them
3838
+ off (even when the client has `labels: true`); ABSENT = the client's option
3839
+ (the off default). A non-boolean `names` is refused
3941
3840
  pre-request.
3942
3841
 
3943
3842
  | field on the row | sibling | example |
@@ -4000,9 +3899,8 @@ name reads per call, so a plain read returns raw ids), through the same
4000
3899
  per-call cap of 20 uncached name reads was hit — names already read are
4001
3900
  cached, so repeating or narrowing the call labels more"`. Unresolvable
4002
3901
  picklists add a note clause and a `reasons` entry per field (the
4003
- field-catalog honesty note / the value-not-served note); on `fetchMany` /
4004
- `fetchByIds` the notes merge across resources and `reasons` keys the plain
4005
- field name. Absent `names` = every sibling was resolved.
3902
+ field-catalog honesty note / the value-not-served note); on `fetchByIds`
3903
+ the notes merge across resources and `reasons` keys the plain field name. Absent `names` = every sibling was resolved.
4006
3904
  - Response size (full model, wrapped names): a tickets row grows ~40% (live,
4007
3905
  100 rows, 31 siblings per row, some of them null: 341,178 B -> 477,325 B;
4008
3906
  more when every FK is populated), a time_entries row ~77% (worst case, every