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
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "schema": "node-autotask n8n consumer mcp projection v1",
3
- "generatedAt": "2026-11-01T01:23:14.190Z",
3
+ "generatedAt": "2026-11-01T10:43:53.479Z",
4
4
  "generator": "scripts/project-n8n-mcp.mjs",
5
5
  "inputs": {
6
6
  "capabilities": "capabilities.json (the capability registry — the single source of truth)",
7
- "plan": "capabilities.plan.json (resources.parentScoped only)",
8
7
  "curation": "n8n-mcp.curation.json (the P3 written rule + the entity subset)",
9
8
  "curationRule": "P3 written rule (n8n consumer subset) — include exactly the entities a daily support workflow actually touches: the ticket lifecycle and its scoped notes; the parties (contacts and companies and their sites); the staff (resources); the effort (time entries) — and only where the tenant has live data (verified data-full tenant: 11,644 tickets, 2026-10-03). The rule is EVIDENCE-BASED, not taste: extend or shrink the subset only via a recorded change to this curation config (the entity entry and its reason are the record); never hand-edit the generated N8N_MCP_MANIFEST.json / N8N_MCP_MANIFEST.md."
10
9
  },
@@ -512,7 +511,7 @@
512
511
  "entity": "ticket",
513
512
  "apiEntity": "tickets",
514
513
  "effectLine": "effect=write (plus destructive only when curation re-includes it)",
515
- "description": "Write ticket records (mode: write — the registry operations with effect=write for tickets: changeStatus, create, openWithNoteAndAssign, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: changeStatus [id, targetStatus]; create [data]; openWithNoteAndAssign [params]; update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
514
+ "description": "Write ticket records (mode: write — the registry operations with effect=write for tickets: changeStatus, create, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: changeStatus [id, targetStatus]; create [data]; update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
516
515
  "annotations": {
517
516
  "readOnlyHint": false,
518
517
  "destructiveHint": false,
@@ -527,7 +526,6 @@
527
526
  "enum": [
528
527
  "changeStatus",
529
528
  "create",
530
- "openWithNoteAndAssign",
531
529
  "update"
532
530
  ]
533
531
  },
@@ -544,15 +542,6 @@
544
542
  "type": "object",
545
543
  "ref": "tickets body (see types)"
546
544
  },
547
- "params": {
548
- "type": "object — { companyID: number | string (the parent company id), title: string, priority?: number (default 3 Low), status?: number (default 1 New), queueID?: number (the node NEVER guesses a queue: omitted + vendor-500 → the typed QUEUE_REQUIRED error with the vendor message + discovery suggestedAction) }"
549
- },
550
- "note": {
551
- "type": "object (optional) — { description: string, title?: string, noteType?: number (default 1), publish?: number (default 1; integer picklist 1/2/4 — a JSON boolean 500s) }"
552
- },
553
- "assign": {
554
- "type": "object (optional) — { resourceID: number | string, roleID: number (required with resourceID) } (prechecked via resources.get BEFORE the create — RESOURCE_INVALID on missing/inactive; the resources table is blind to the API user, get is the authority)"
555
- },
556
545
  "dry_run": {
557
546
  "type": "boolean",
558
547
  "default": true,
@@ -635,41 +624,6 @@
635
624
  "bounds": "Dry-run first (dry_run).",
636
625
  "dryRun": true
637
626
  },
638
- {
639
- "op": "tickets.openWithNoteAndAssign",
640
- "verb": "openWithNoteAndAssign",
641
- "effect": "write",
642
- "kind": "helper",
643
- "flags": [],
644
- "requiredArgs": [
645
- "params"
646
- ],
647
- "input": {
648
- "properties": {
649
- "params": {
650
- "type": "object — { companyID: number | string (the parent company id), title: string, priority?: number (default 3 Low), status?: number (default 1 New), queueID?: number (the node NEVER guesses a queue: omitted + vendor-500 → the typed QUEUE_REQUIRED error with the vendor message + discovery suggestedAction) }"
651
- },
652
- "note": {
653
- "type": "object (optional) — { description: string, title?: string, noteType?: number (default 1), publish?: number (default 1; integer picklist 1/2/4 — a JSON boolean 500s) }"
654
- },
655
- "assign": {
656
- "type": "object (optional) — { resourceID: number | string, roleID: number (required with resourceID) } (prechecked via resources.get BEFORE the create — RESOURCE_INVALID on missing/inactive; the resources table is blind to the API user, get is the authority)"
657
- }
658
- },
659
- "required": [
660
- "params"
661
- ]
662
- },
663
- "annotations": {
664
- "readOnlyHint": false,
665
- "destructiveHint": false,
666
- "idempotentHint": false,
667
- "openWorldHint": true
668
- },
669
- "purpose": "Open a ticket with an optional first scoped note and an optional assignment in ONE op — the node absorbs the ww29-proven create contract and the note/assignment wire choreography (6 calls today → 1).",
670
- "bounds": "Dry-run first (dry_run).",
671
- "dryRun": true
672
- },
673
627
  {
674
628
  "op": "tickets.update",
675
629
  "verb": "update",
@@ -711,7 +665,6 @@
711
665
  "backingOperations": [
712
666
  "tickets.changeStatus",
713
667
  "tickets.create",
714
- "tickets.openWithNoteAndAssign",
715
668
  "tickets.update"
716
669
  ]
717
670
  },
@@ -721,7 +674,7 @@
721
674
  "entity": "contact",
722
675
  "apiEntity": "contacts",
723
676
  "effectLine": "effect=read only — zero mutating requests, ungated by design",
724
- "description": "Read contact records (mode: read — effect=read operations only; zero mutating requests, ungated by design). Registry operations for contacts: count: Count contact records matching a JSON filter. (under a company) | findByEmail: Find a contact by email address. | get: Get one Contact by id. | getContext: Get a contact with its primary context records in one call. (under a company) | getEntityInfo: Get the Contact capability metadata (canCreate/canUpdate/hasUserDefinedFields/...). | getFieldInfo: Get the Contact field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog). | getUdfInfo: Get the Contact user-defined-field catalog. | query: Query Contact records with a JSON filter. | resolve: Resolve a Contact from a numeric id (the direct fetch, tried first) or an exact value of its primary text field(s) (firstName + lastName — exact match only). Returns the Contact record with the numeric id for get/update/delete. Other identifier forms (e.g. an email address) are NOT tried by resolve — use the resource's dedicated lookup op where one exists (see autotask_catalog). | search: Search contact records by text (contains on the searchable fields). (under a company). Bounds: Sensitive. Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows. Sensitive. Bounded: limit default 25, max 500. Sensitive. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
677
+ "description": "Read contact records (mode: read — effect=read operations only; zero mutating requests, ungated by design). Registry operations for contacts: count: Count contact records matching a JSON filter. (under a company) | findByEmail: Find a contact by email address. | fullProfile: Get a contact with its company and its open tickets in a single call: the contact (the parent-scoped root read, or the stage-1 row when the contact has no company FK), its company, and its open tickets — every part separately reported. | get: Get one Contact by id. | getContext: Get a contact with its primary context records in one call. (under a company) | getEntityInfo: Get the Contact capability metadata (canCreate/canUpdate/hasUserDefinedFields/...). | getFieldInfo: Get the Contact field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog). | getUdfInfo: Get the Contact user-defined-field catalog. | query: Query Contact records with a JSON filter. | resolve: Resolve a Contact from a numeric id (the direct fetch, tried first) or an exact value of its primary text field(s) (firstName + lastName — exact match only). Returns the Contact record with the numeric id for get/update/delete. Other identifier forms (e.g. an email address) are NOT tried by resolve — use the resource's dedicated lookup op where one exists (see autotask_catalog). | search: Search contact records by text (contains on the searchable fields). (under a company). Bounds: Sensitive. Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows. Sensitive. Bounded: limit default 25, max 500. Sensitive. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
725
678
  "annotations": {
726
679
  "readOnlyHint": true,
727
680
  "destructiveHint": false,
@@ -790,6 +743,34 @@
790
743
  "purpose": "Find a contact by email address.",
791
744
  "bounds": "Sensitive."
792
745
  },
746
+ {
747
+ "op": "contacts.fullProfile",
748
+ "verb": "fullProfile",
749
+ "effect": "read",
750
+ "kind": "helper",
751
+ "flags": [],
752
+ "requiredArgs": [
753
+ "id"
754
+ ],
755
+ "input": {
756
+ "properties": {
757
+ "id": {
758
+ "type": "number | string"
759
+ }
760
+ },
761
+ "required": [
762
+ "id"
763
+ ]
764
+ },
765
+ "annotations": {
766
+ "readOnlyHint": true,
767
+ "destructiveHint": false,
768
+ "idempotentHint": true,
769
+ "openWorldHint": true
770
+ },
771
+ "purpose": "Get a contact with its company and its open tickets in a single call: the contact (the parent-scoped root read, or the stage-1 row when the contact has no company FK), its company, and its open tickets — every part separately reported.",
772
+ "bounds": ""
773
+ },
793
774
  {
794
775
  "op": "contacts.get",
795
776
  "verb": "get",
@@ -1026,6 +1007,7 @@
1026
1007
  "backingOperations": [
1027
1008
  "contacts.count",
1028
1009
  "contacts.findByEmail",
1010
+ "contacts.fullProfile",
1029
1011
  "contacts.get",
1030
1012
  "contacts.getContext",
1031
1013
  "contacts.getEntityInfo",
@@ -1042,7 +1024,7 @@
1042
1024
  "entity": "contact",
1043
1025
  "apiEntity": "contacts",
1044
1026
  "effectLine": "effect=write (plus destructive only when curation re-includes it)",
1045
- "description": "Write contact records (mode: write — the registry operations with effect=write for contacts: create, moveToCompany, update, upsertByEmail). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Approval-gated operations (contacts.moveToCompany) additionally require confirm equal to the operation key. Per-operation required arguments: create [parentId, data]; moveToCompany [sourceContactId, destinationCompanyId]; update [parentId, id, data]; upsertByEmail [companyID, email]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
1027
+ "description": "Write contact records (mode: write — the registry operations with effect=write for contacts: create, moveToCompany, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Approval-gated operations (contacts.moveToCompany) additionally require confirm equal to the operation key. Per-operation required arguments: create [parentId, data]; moveToCompany [sourceContactId, destinationCompanyId]; update [parentId, id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
1046
1028
  "annotations": {
1047
1029
  "readOnlyHint": false,
1048
1030
  "destructiveHint": false,
@@ -1057,8 +1039,7 @@
1057
1039
  "enum": [
1058
1040
  "create",
1059
1041
  "moveToCompany",
1060
- "update",
1061
- "upsertByEmail"
1042
+ "update"
1062
1043
  ]
1063
1044
  },
1064
1045
  "parentId": {
@@ -1077,15 +1058,6 @@
1077
1058
  "id": {
1078
1059
  "type": "number | string"
1079
1060
  },
1080
- "companyID": {
1081
- "type": "number | string (the parent — the create MUST be scoped: the global /V1.0/Contacts collection has no POST)"
1082
- },
1083
- "email": {
1084
- "type": "string (the natural dedup key — the vendor field emailAddress, exact match under the parent)"
1085
- },
1086
- "extra": {
1087
- "type": "object (optional) — { firstName?: string (default: the email local-part), lastName?: string (default: empty string), isActive?: boolean | number (BOOLEAN ACCEPTED — mapped to the zone-correct number 1/0; a raw 0/1 number passes through) }"
1088
- },
1089
1061
  "dry_run": {
1090
1062
  "type": "boolean",
1091
1063
  "default": true,
@@ -1216,50 +1188,12 @@
1216
1188
  "purpose": "Update a contact. (under a company)",
1217
1189
  "bounds": "Dry-run first (dry_run). Idempotent.",
1218
1190
  "dryRun": true
1219
- },
1220
- {
1221
- "op": "contacts.upsertByEmail",
1222
- "verb": "upsertByEmail",
1223
- "effect": "write",
1224
- "kind": "helper",
1225
- "flags": [],
1226
- "requiredArgs": [
1227
- "companyID",
1228
- "email"
1229
- ],
1230
- "input": {
1231
- "properties": {
1232
- "companyID": {
1233
- "type": "number | string (the parent — the create MUST be scoped: the global /V1.0/Contacts collection has no POST)"
1234
- },
1235
- "email": {
1236
- "type": "string (the natural dedup key — the vendor field emailAddress, exact match under the parent)"
1237
- },
1238
- "extra": {
1239
- "type": "object (optional) — { firstName?: string (default: the email local-part), lastName?: string (default: empty string), isActive?: boolean | number (BOOLEAN ACCEPTED — mapped to the zone-correct number 1/0; a raw 0/1 number passes through) }"
1240
- }
1241
- },
1242
- "required": [
1243
- "companyID",
1244
- "email"
1245
- ]
1246
- },
1247
- "annotations": {
1248
- "readOnlyHint": false,
1249
- "destructiveHint": false,
1250
- "idempotentHint": false,
1251
- "openWorldHint": true
1252
- },
1253
- "purpose": "Make sure a contact with the given email exists under the given company — find-or-create under the RIGHT parent, with the typed EXISTS/CREATED result (+ the record).",
1254
- "bounds": "Dry-run first (dry_run).",
1255
- "dryRun": true
1256
1191
  }
1257
1192
  ],
1258
1193
  "backingOperations": [
1259
1194
  "contacts.create",
1260
1195
  "contacts.moveToCompany",
1261
- "contacts.update",
1262
- "contacts.upsertByEmail"
1196
+ "contacts.update"
1263
1197
  ]
1264
1198
  },
1265
1199
  {
@@ -1680,7 +1614,7 @@
1680
1614
  "entity": "company",
1681
1615
  "apiEntity": "companies",
1682
1616
  "effectLine": "effect=write (plus destructive only when curation re-includes it)",
1683
- "description": "Write company records (mode: write — the registry operations with effect=write for companies: create, update, upsertByName). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: create [data]; update [id, data]; upsertByName [companyName, params]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
1617
+ "description": "Write company records (mode: write — the registry operations with effect=write for companies: create, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: create [data]; update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
1684
1618
  "annotations": {
1685
1619
  "readOnlyHint": false,
1686
1620
  "destructiveHint": false,
@@ -1694,8 +1628,7 @@
1694
1628
  "type": "string",
1695
1629
  "enum": [
1696
1630
  "create",
1697
- "update",
1698
- "upsertByName"
1631
+ "update"
1699
1632
  ]
1700
1633
  },
1701
1634
  "data": {
@@ -1705,12 +1638,6 @@
1705
1638
  "id": {
1706
1639
  "type": "number | string"
1707
1640
  },
1708
- "companyName": {
1709
- "type": "string (the natural dedup key — exact match on the vendor field companyName; the dedup covers only companies this API user can enumerate — the unowned surface is blind)"
1710
- },
1711
- "params": {
1712
- "type": "object — { phone: string, ownerResourceID?: number (REQUIRED on the create path — an active non-API-only resource; the node cannot enumerate candidates: the resources table is blind to the API user; absent + a dedup miss → the typed OWNER_RESOURCE_REQUIRED, zero wire), companyType?: number (default 1 — the ww29-proven value) }"
1713
- },
1714
1641
  "dry_run": {
1715
1642
  "type": "boolean",
1716
1643
  "default": true,
@@ -1792,46 +1719,11 @@
1792
1719
  "purpose": "Update a company.",
1793
1720
  "bounds": "Dry-run first (dry_run). Idempotent.",
1794
1721
  "dryRun": true
1795
- },
1796
- {
1797
- "op": "companies.upsertByName",
1798
- "verb": "upsertByName",
1799
- "effect": "write",
1800
- "kind": "helper",
1801
- "flags": [],
1802
- "requiredArgs": [
1803
- "companyName",
1804
- "params"
1805
- ],
1806
- "input": {
1807
- "properties": {
1808
- "companyName": {
1809
- "type": "string (the natural dedup key — exact match on the vendor field companyName; the dedup covers only companies this API user can enumerate — the unowned surface is blind)"
1810
- },
1811
- "params": {
1812
- "type": "object — { phone: string, ownerResourceID?: number (REQUIRED on the create path — an active non-API-only resource; the node cannot enumerate candidates: the resources table is blind to the API user; absent + a dedup miss → the typed OWNER_RESOURCE_REQUIRED, zero wire), companyType?: number (default 1 — the ww29-proven value) }"
1813
- }
1814
- },
1815
- "required": [
1816
- "companyName",
1817
- "params"
1818
- ]
1819
- },
1820
- "annotations": {
1821
- "readOnlyHint": false,
1822
- "destructiveHint": false,
1823
- "idempotentHint": false,
1824
- "openWorldHint": true
1825
- },
1826
- "purpose": "Make sure a company with the given name exists — find-or-create that carries the blind-surface boundary IN THE RESULT instead of pretending total visibility.",
1827
- "bounds": "Dry-run first (dry_run).",
1828
- "dryRun": true
1829
1722
  }
1830
1723
  ],
1831
1724
  "backingOperations": [
1832
1725
  "companies.create",
1833
- "companies.update",
1834
- "companies.upsertByName"
1726
+ "companies.update"
1835
1727
  ]
1836
1728
  },
1837
1729
  {
@@ -2554,7 +2446,7 @@
2554
2446
  "entity": "resource",
2555
2447
  "apiEntity": "resources",
2556
2448
  "effectLine": "effect=read only — zero mutating requests, ungated by design",
2557
- "description": "Read resource records (mode: read — effect=read operations only; zero mutating requests, ungated by design). Registry operations for resources: count: Count resource records matching a JSON filter. | get: Get one Resource by id. | getEntityInfo: Get the Resource capability metadata (canCreate/canUpdate/hasUserDefinedFields/...). | getFieldInfo: Get the Resource field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog). | getUdfInfo: Get the Resource user-defined-field catalog. | query: Query Resource records with a JSON filter. | resolve: Resolve a resource from a numeric id (the direct fetch, tried first) or an exact value of its primary text field(s) (firstName + lastName — exact match only). Returns the Resource record with the numeric id for get/update/delete. Other identifier forms (e.g. an email address) are NOT tried by resolve — use the resource's dedicated lookup op where one exists (see autotask_catalog). | search: Search resource records by text (contains on the searchable fields). | whoAmI: Identify the resource the current credentials act as.. Bounds: Sensitive. Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows. Sensitive. Bounded: limit default 25, max 500. Sensitive. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
2449
+ "description": "Read resource records (mode: read — effect=read operations only; zero mutating requests, ungated by design). Registry operations for resources: count: Count resource records matching a JSON filter. | get: Get one Resource by id. | getEntityInfo: Get the Resource capability metadata (canCreate/canUpdate/hasUserDefinedFields/...). | getFieldInfo: Get the Resource field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog). | getUdfInfo: Get the Resource user-defined-field catalog. | query: Query Resource records with a JSON filter. | resolve: Resolve a resource from a numeric id (the direct fetch, tried first) or an exact value of its primary text field(s) (firstName + lastName — exact match only). Returns the Resource record with the numeric id for get/update/delete. Other identifier forms (e.g. an email address) are NOT tried by resolve — use the resource's dedicated lookup op where one exists (see autotask_catalog). | search: Search resource records by text (contains on the searchable fields). | whoAmI: Identify the resource the current credentials act as. | workload: Get a resource's workload in a single call: its open tickets (rows + the exact open totals), its task assignments and the tasks behind them, its time-off balances and requests.. Bounds: Sensitive. Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows. Sensitive. Bounded: limit default 25, max 500. Sensitive. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
2558
2450
  "annotations": {
2559
2451
  "readOnlyHint": true,
2560
2452
  "destructiveHint": false,
@@ -2811,6 +2703,34 @@
2811
2703
  },
2812
2704
  "purpose": "Identify the resource the current credentials act as.",
2813
2705
  "bounds": "Sensitive."
2706
+ },
2707
+ {
2708
+ "op": "resources.workload",
2709
+ "verb": "workload",
2710
+ "effect": "read",
2711
+ "kind": "helper",
2712
+ "flags": [],
2713
+ "requiredArgs": [
2714
+ "id"
2715
+ ],
2716
+ "input": {
2717
+ "properties": {
2718
+ "id": {
2719
+ "type": "number | string"
2720
+ }
2721
+ },
2722
+ "required": [
2723
+ "id"
2724
+ ]
2725
+ },
2726
+ "annotations": {
2727
+ "readOnlyHint": true,
2728
+ "destructiveHint": false,
2729
+ "idempotentHint": true,
2730
+ "openWorldHint": true
2731
+ },
2732
+ "purpose": "Get a resource's workload in a single call: its open tickets (rows + the exact open totals), its task assignments and the tasks behind them, its time-off balances and requests.",
2733
+ "bounds": ""
2814
2734
  }
2815
2735
  ],
2816
2736
  "backingOperations": [
@@ -2822,7 +2742,8 @@
2822
2742
  "resources.query",
2823
2743
  "resources.resolve",
2824
2744
  "resources.search",
2825
- "resources.whoAmI"
2745
+ "resources.whoAmI",
2746
+ "resources.workload"
2826
2747
  ]
2827
2748
  },
2828
2749
  {
@@ -2831,11 +2752,11 @@
2831
2752
  "entity": "resource",
2832
2753
  "apiEntity": "resources",
2833
2754
  "effectLine": "effect=write (plus destructive only when curation re-includes it)",
2834
- "description": "Write resource records (mode: write — the registry operations with effect=write for resources: transferOwnership, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Approval-gated operations (resources.transferOwnership) additionally require confirm equal to the operation key. Per-operation required arguments: transferOwnership [sourceResourceIds, destinationResourceId]; update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
2755
+ "description": "Write resource records (mode: write — the registry operations with effect=write for resources: update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
2835
2756
  "annotations": {
2836
2757
  "readOnlyHint": false,
2837
2758
  "destructiveHint": false,
2838
- "idempotentHint": false,
2759
+ "idempotentHint": true,
2839
2760
  "openWorldHint": true
2840
2761
  },
2841
2762
  "inputSchema": {
@@ -2844,16 +2765,9 @@
2844
2765
  "operation": {
2845
2766
  "type": "string",
2846
2767
  "enum": [
2847
- "transferOwnership",
2848
2768
  "update"
2849
2769
  ]
2850
2770
  },
2851
- "sourceResourceIds": {
2852
- "type": "(number | string)[] (max 500)"
2853
- },
2854
- "destinationResourceId": {
2855
- "type": "number | string"
2856
- },
2857
2771
  "id": {
2858
2772
  "type": "number | string"
2859
2773
  },
@@ -2876,45 +2790,6 @@
2876
2790
  ]
2877
2791
  },
2878
2792
  "operations": [
2879
- {
2880
- "op": "resources.transferOwnership",
2881
- "verb": "transferOwnership",
2882
- "effect": "write",
2883
- "kind": "helper",
2884
- "flags": [
2885
- "requiresApproval",
2886
- "sensitive"
2887
- ],
2888
- "requiredArgs": [
2889
- "sourceResourceIds",
2890
- "destinationResourceId"
2891
- ],
2892
- "input": {
2893
- "properties": {
2894
- "sourceResourceIds": {
2895
- "type": "(number | string)[] (max 500)"
2896
- },
2897
- "destinationResourceId": {
2898
- "type": "number | string"
2899
- }
2900
- },
2901
- "required": [
2902
- "sourceResourceIds",
2903
- "destinationResourceId"
2904
- ]
2905
- },
2906
- "annotations": {
2907
- "readOnlyHint": false,
2908
- "destructiveHint": false,
2909
- "idempotentHint": false,
2910
- "openWorldHint": true
2911
- },
2912
- "purpose": "Transfer ownership of named resources to another resource.",
2913
- "bounds": "Dry-run first (dry_run). Requires confirmation (confirm: resources.transferOwnership). Sensitive.",
2914
- "dryRun": true,
2915
- "confirmRequired": true,
2916
- "confirmValue": "resources.transferOwnership"
2917
- },
2918
2793
  {
2919
2794
  "op": "resources.update",
2920
2795
  "verb": "update",
@@ -2955,7 +2830,6 @@
2955
2830
  }
2956
2831
  ],
2957
2832
  "backingOperations": [
2958
- "resources.transferOwnership",
2959
2833
  "resources.update"
2960
2834
  ]
2961
2835
  },
@@ -2965,7 +2839,7 @@
2965
2839
  "entity": "timeEntry",
2966
2840
  "apiEntity": "time_entries",
2967
2841
  "effectLine": "effect=read only — zero mutating requests, ungated by design",
2968
- "description": "Read timeEntry records (mode: read — effect=read operations only; zero mutating requests, ungated by design). Registry operations for time_entries: count: Count time entry records matching a JSON filter. | customRange: Get time entries for an explicit dateWorked range. | dateWorkedWindow: The unambiguous-named alias for time_entries.postedWithin (issue #66): the dateWorked window read with the posted/unposted split derived from the billing_items.timeEntryID join — same wire, same result (incl. truncated). The window is ON dateWorked (entries worked within [from, to]); \"postedWithin\" keeps its historical name (its \"posted\" is the posted/unposted split, never a posted-date window or a postedDate read). | get: Get one TimeEntry by id. | getEntityInfo: Get the TimeEntry capability metadata (canCreate/canUpdate/hasUserDefinedFields/...). | getFieldInfo: Get the TimeEntry field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog). | getPosted: Get posted time entries (an entry that a billing item references), optionally for one ticket and/or a dateWorked window — posted state derives from the billing_items.timeEntryID join, never a postedDate read or approval. | getUdfInfo: Get the TimeEntry user-defined-field catalog. | getUnposted: Get unposted time entries (entries no billing item references), optionally for one ticket and/or a dateWorked window — posted state derives from the billing_items.timeEntryID join, never a postedDate read or approval. | last14: Get time entries for the last 14 days (rolling, dateWorked). | last24h: Get time entries for the last 24 hours (rolling, dateWorked). | last30: Get time entries for the last 30 days (rolling, dateWorked). | last7: Get time entries for the last 7 days (rolling, dateWorked). | last90: Get time entries for the last 90 days (rolling, dateWorked). | lastFullFortnight: Get time entries for the last complete fortnight. | lastFullMonth: Get time entries for the last complete calendar month. | lastFullQuarter: Get time entries for the last complete calendar quarter. | lastFullWeek: Get time entries for the last complete calendar week. | postedWithin: Get time entries WORKED within an inclusive dateWorked window with the posted/unposted split. The window is ON the dateWorked field — the name's \"posted\" is the posted/unposted SPLIT derived from the billing_items.timeEntryID join (an entry a billing item references is posted, approval is not consulted), never a posted-date window or a postedDate read. The counts cover the returned rows only; truncated flags a capped read. time_entries.dateWorkedWindow is the unambiguous name. | query: Query TimeEntry records with a JSON filter. | resolve: Resolve a time entry from a numeric id (the direct fetch) only — this resource has no searchable text field, so a non-numeric identifier throws a config error. | today: Get time entries for today (dateWorked = now, server date). | yesterday: Get time entries for yesterday (dateWorked = yesterday, server date).. Bounds: Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
2842
+ "description": "Read timeEntry records (mode: read — effect=read operations only; zero mutating requests, ungated by design). Registry operations for time_entries: count: Count time entry records matching a JSON filter. | dateWorkedWindow: The ONE posted-state read (issue #290): a bounded read over the inclusive dateWorked window [from, to] (UTC 'YYYY-MM-DD' days) with the posted/unposted split derived from the billing_items.timeEntryID join — never a postedDate read (W-D3: this zone's TimeEntry entity has no postedDate; postedDate exist|notExist 500s, live ww29 2026-10-27). Approval is NOT consulted (ww29 never populates billingApprovalDateTime yet bills those entries). The no-window form is gone: read with no dateWorked filter goes through time_entries.query. | get: Get one TimeEntry by id. | getEntityInfo: Get the TimeEntry capability metadata (canCreate/canUpdate/hasUserDefinedFields/...). | getFieldInfo: Get the TimeEntry field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog). | getUdfInfo: Get the TimeEntry user-defined-field catalog. | query: Query TimeEntry records with a JSON filter. | resolve: Resolve a time entry from a numeric id (the direct fetch) only — this resource has no searchable text field, so a non-numeric identifier throws a config error.. Bounds: Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
2969
2843
  "annotations": {
2970
2844
  "readOnlyHint": true,
2971
2845
  "destructiveHint": false,
@@ -3002,44 +2876,6 @@
3002
2876
  "purpose": "Count time entry records matching a JSON filter.",
3003
2877
  "bounds": ""
3004
2878
  },
3005
- {
3006
- "op": "time_entries.customRange",
3007
- "verb": "customRange",
3008
- "effect": "read",
3009
- "kind": "helper",
3010
- "flags": [],
3011
- "requiredArgs": [
3012
- "from",
3013
- "to"
3014
- ],
3015
- "input": {
3016
- "properties": {
3017
- "from": {
3018
- "type": "string (UTC date-time)"
3019
- },
3020
- "to": {
3021
- "type": "string (UTC date-time)"
3022
- },
3023
- "limit": {
3024
- "type": "number",
3025
- "default": 500,
3026
- "max": 500
3027
- }
3028
- },
3029
- "required": [
3030
- "from",
3031
- "to"
3032
- ]
3033
- },
3034
- "annotations": {
3035
- "readOnlyHint": true,
3036
- "destructiveHint": false,
3037
- "idempotentHint": true,
3038
- "openWorldHint": true
3039
- },
3040
- "purpose": "Get time entries for an explicit dateWorked range.",
3041
- "bounds": ""
3042
- },
3043
2879
  {
3044
2880
  "op": "time_entries.dateWorkedWindow",
3045
2881
  "verb": "dateWorkedWindow",
@@ -3053,10 +2889,10 @@
3053
2889
  "input": {
3054
2890
  "properties": {
3055
2891
  "from": {
3056
- "type": "string (UTC date, inclusive — the dateWorked window start: the from-day start) — opts.tz never shifts it (dateWorked is date-only)"
2892
+ "type": "string (UTC date, inclusive — the dateWorked window start: the from-day start; the UTC calendar days ARE the wire bounds — dateWorked is date-only at 00:00 UTC, so nothing is ever shifted)"
3057
2893
  },
3058
2894
  "to": {
3059
- "type": "string (UTC date, inclusive — the dateWorked window end: the to-day end) — opts.tz never shifts it (dateWorked is date-only). The unambiguous-named alias for postedWithin (same wire, same result incl. truncated — the name names the dateWorked window the counts cover). Result { items, postedCount, unpostedCount, window, nextCursor, unconfirmed, truncated } — the counts cover the returned rows only (at most limit); opts.cursor pages past 500 entries, opts.countOnly returns whole-window totals { window, total, postedCount, unpostedCount, vendorCalls, estimate, budget } under opts.maxVendorCalls (default 30, max 200; BUDGET_EXCEEDED refusal)"
2895
+ "type": "string (UTC date, inclusive — the dateWorked window end: the to-day end). One bounded dateWorked read (limit 1-500, default 500 — the hard cap) plus at most 2 billing_items joins (250 ids each, every read entry); the window is ON the dateWorked field (the posted narrowing is a client-side filter after the join); opts.state ('posted' | 'unposted', omitted = both) narrows items client-side after the join (no postedDate condition is ever sent), opts.ticketID / opts.resourceID narrow the read server-side. Result { items, postedCount, unpostedCount, window, nextCursor, unconfirmed, truncated } — the counts cover the returned rows only (at most limit); opts.cursor pages past 500 entries, opts.countOnly returns whole-window totals { window, total, postedCount, unpostedCount, vendorCalls, estimate, budget } under opts.maxVendorCalls (default 30, max 200; BUDGET_EXCEEDED refusal); truncated is true when the entry read has a live next-page cursor (more matching rows exist) or a join page was truncated"
3060
2896
  }
3061
2897
  },
3062
2898
  "required": [
@@ -3070,7 +2906,7 @@
3070
2906
  "idempotentHint": true,
3071
2907
  "openWorldHint": true
3072
2908
  },
3073
- "purpose": "The unambiguous-named alias for time_entries.postedWithin (issue #66): the dateWorked window read with the posted/unposted split derived from the billing_items.timeEntryID join — same wire, same result (incl. truncated). The window is ON dateWorked (entries worked within [from, to]); \"postedWithin\" keeps its historical name (its \"posted\" is the posted/unposted split, never a posted-date window or a postedDate read).",
2909
+ "purpose": "The ONE posted-state read (issue #290): a bounded read over the inclusive dateWorked window [from, to] (UTC 'YYYY-MM-DD' days) with the posted/unposted split derived from the billing_items.timeEntryID join — never a postedDate read (W-D3: this zone's TimeEntry entity has no postedDate; postedDate exist|notExist 500s, live ww29 2026-10-27). Approval is NOT consulted (ww29 never populates billingApprovalDateTime yet bills those entries). The no-window form is gone: read with no dateWorked filter goes through time_entries.query.",
3074
2910
  "bounds": ""
3075
2911
  },
3076
2912
  {
@@ -3141,26 +2977,6 @@
3141
2977
  "purpose": "Get the TimeEntry field catalog (types, picklists, required, maxLength) — the full raw field dump (the largest read of the info trio; compact discovery: operations.fieldCatalog).",
3142
2978
  "bounds": ""
3143
2979
  },
3144
- {
3145
- "op": "time_entries.getPosted",
3146
- "verb": "getPosted",
3147
- "effect": "read",
3148
- "kind": "helper",
3149
- "flags": [],
3150
- "requiredArgs": [],
3151
- "input": {
3152
- "properties": {},
3153
- "required": []
3154
- },
3155
- "annotations": {
3156
- "readOnlyHint": true,
3157
- "destructiveHint": false,
3158
- "idempotentHint": true,
3159
- "openWorldHint": true
3160
- },
3161
- "purpose": "Get posted time entries (an entry that a billing item references), optionally for one ticket and/or a dateWorked window — posted state derives from the billing_items.timeEntryID join, never a postedDate read or approval.",
3162
- "bounds": ""
3163
- },
3164
2980
  {
3165
2981
  "op": "time_entries.getUdfInfo",
3166
2982
  "verb": "getUdfInfo",
@@ -3182,14 +2998,28 @@
3182
2998
  "bounds": ""
3183
2999
  },
3184
3000
  {
3185
- "op": "time_entries.getUnposted",
3186
- "verb": "getUnposted",
3001
+ "op": "time_entries.query",
3002
+ "verb": "query",
3187
3003
  "effect": "read",
3188
- "kind": "helper",
3004
+ "kind": "primitive",
3189
3005
  "flags": [],
3190
3006
  "requiredArgs": [],
3191
3007
  "input": {
3192
- "properties": {},
3008
+ "properties": {
3009
+ "filter": {
3010
+ "type": "FilterCondition[] (13 operators + and/or; autotask_describe for the exact grammar)"
3011
+ },
3012
+ "limit": {
3013
+ "type": "number",
3014
+ "default": 500,
3015
+ "max": 2500,
3016
+ "note": "TOTAL records (server-side pagination; not a page size)"
3017
+ },
3018
+ "includeFields": {
3019
+ "type": "string[]",
3020
+ "note": "field subset to return (vendor API casing; omit = full model — pass to save context)"
3021
+ }
3022
+ },
3193
3023
  "required": []
3194
3024
  },
3195
3025
  "annotations": {
@@ -3198,19 +3028,27 @@
3198
3028
  "idempotentHint": true,
3199
3029
  "openWorldHint": true
3200
3030
  },
3201
- "purpose": "Get unposted time entries (entries no billing item references), optionally for one ticket and/or a dateWorked window — posted state derives from the billing_items.timeEntryID join, never a postedDate read or approval.",
3202
- "bounds": ""
3031
+ "purpose": "Query TimeEntry records with a JSON filter.",
3032
+ "bounds": "Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows."
3203
3033
  },
3204
3034
  {
3205
- "op": "time_entries.last14",
3206
- "verb": "last14",
3035
+ "op": "time_entries.resolve",
3036
+ "verb": "resolve",
3207
3037
  "effect": "read",
3208
3038
  "kind": "helper",
3209
3039
  "flags": [],
3210
- "requiredArgs": [],
3040
+ "requiredArgs": [
3041
+ "identifier"
3042
+ ],
3211
3043
  "input": {
3212
- "properties": {},
3213
- "required": []
3044
+ "properties": {
3045
+ "identifier": {
3046
+ "type": "number | string"
3047
+ }
3048
+ },
3049
+ "required": [
3050
+ "identifier"
3051
+ ]
3214
3052
  },
3215
3053
  "annotations": {
3216
3054
  "readOnlyHint": true,
@@ -3218,329 +3056,19 @@
3218
3056
  "idempotentHint": true,
3219
3057
  "openWorldHint": true
3220
3058
  },
3221
- "purpose": "Get time entries for the last 14 days (rolling, dateWorked).",
3222
- "bounds": ""
3223
- },
3224
- {
3225
- "op": "time_entries.last24h",
3226
- "verb": "last24h",
3227
- "effect": "read",
3228
- "kind": "helper",
3229
- "flags": [],
3230
- "requiredArgs": [],
3231
- "input": {
3232
- "properties": {},
3233
- "required": []
3234
- },
3235
- "annotations": {
3236
- "readOnlyHint": true,
3237
- "destructiveHint": false,
3238
- "idempotentHint": true,
3239
- "openWorldHint": true
3240
- },
3241
- "purpose": "Get time entries for the last 24 hours (rolling, dateWorked).",
3242
- "bounds": ""
3243
- },
3244
- {
3245
- "op": "time_entries.last30",
3246
- "verb": "last30",
3247
- "effect": "read",
3248
- "kind": "helper",
3249
- "flags": [],
3250
- "requiredArgs": [],
3251
- "input": {
3252
- "properties": {},
3253
- "required": []
3254
- },
3255
- "annotations": {
3256
- "readOnlyHint": true,
3257
- "destructiveHint": false,
3258
- "idempotentHint": true,
3259
- "openWorldHint": true
3260
- },
3261
- "purpose": "Get time entries for the last 30 days (rolling, dateWorked).",
3262
- "bounds": ""
3263
- },
3264
- {
3265
- "op": "time_entries.last7",
3266
- "verb": "last7",
3267
- "effect": "read",
3268
- "kind": "helper",
3269
- "flags": [],
3270
- "requiredArgs": [],
3271
- "input": {
3272
- "properties": {},
3273
- "required": []
3274
- },
3275
- "annotations": {
3276
- "readOnlyHint": true,
3277
- "destructiveHint": false,
3278
- "idempotentHint": true,
3279
- "openWorldHint": true
3280
- },
3281
- "purpose": "Get time entries for the last 7 days (rolling, dateWorked).",
3282
- "bounds": ""
3283
- },
3284
- {
3285
- "op": "time_entries.last90",
3286
- "verb": "last90",
3287
- "effect": "read",
3288
- "kind": "helper",
3289
- "flags": [],
3290
- "requiredArgs": [],
3291
- "input": {
3292
- "properties": {},
3293
- "required": []
3294
- },
3295
- "annotations": {
3296
- "readOnlyHint": true,
3297
- "destructiveHint": false,
3298
- "idempotentHint": true,
3299
- "openWorldHint": true
3300
- },
3301
- "purpose": "Get time entries for the last 90 days (rolling, dateWorked).",
3302
- "bounds": ""
3303
- },
3304
- {
3305
- "op": "time_entries.lastFullFortnight",
3306
- "verb": "lastFullFortnight",
3307
- "effect": "read",
3308
- "kind": "helper",
3309
- "flags": [],
3310
- "requiredArgs": [],
3311
- "input": {
3312
- "properties": {},
3313
- "required": []
3314
- },
3315
- "annotations": {
3316
- "readOnlyHint": true,
3317
- "destructiveHint": false,
3318
- "idempotentHint": true,
3319
- "openWorldHint": true
3320
- },
3321
- "purpose": "Get time entries for the last complete fortnight.",
3322
- "bounds": ""
3323
- },
3324
- {
3325
- "op": "time_entries.lastFullMonth",
3326
- "verb": "lastFullMonth",
3327
- "effect": "read",
3328
- "kind": "helper",
3329
- "flags": [],
3330
- "requiredArgs": [],
3331
- "input": {
3332
- "properties": {},
3333
- "required": []
3334
- },
3335
- "annotations": {
3336
- "readOnlyHint": true,
3337
- "destructiveHint": false,
3338
- "idempotentHint": true,
3339
- "openWorldHint": true
3340
- },
3341
- "purpose": "Get time entries for the last complete calendar month.",
3342
- "bounds": ""
3343
- },
3344
- {
3345
- "op": "time_entries.lastFullQuarter",
3346
- "verb": "lastFullQuarter",
3347
- "effect": "read",
3348
- "kind": "helper",
3349
- "flags": [],
3350
- "requiredArgs": [],
3351
- "input": {
3352
- "properties": {},
3353
- "required": []
3354
- },
3355
- "annotations": {
3356
- "readOnlyHint": true,
3357
- "destructiveHint": false,
3358
- "idempotentHint": true,
3359
- "openWorldHint": true
3360
- },
3361
- "purpose": "Get time entries for the last complete calendar quarter.",
3362
- "bounds": ""
3363
- },
3364
- {
3365
- "op": "time_entries.lastFullWeek",
3366
- "verb": "lastFullWeek",
3367
- "effect": "read",
3368
- "kind": "helper",
3369
- "flags": [],
3370
- "requiredArgs": [],
3371
- "input": {
3372
- "properties": {},
3373
- "required": []
3374
- },
3375
- "annotations": {
3376
- "readOnlyHint": true,
3377
- "destructiveHint": false,
3378
- "idempotentHint": true,
3379
- "openWorldHint": true
3380
- },
3381
- "purpose": "Get time entries for the last complete calendar week.",
3382
- "bounds": ""
3383
- },
3384
- {
3385
- "op": "time_entries.postedWithin",
3386
- "verb": "postedWithin",
3387
- "effect": "read",
3388
- "kind": "helper",
3389
- "flags": [],
3390
- "requiredArgs": [
3391
- "from",
3392
- "to"
3393
- ],
3394
- "input": {
3395
- "properties": {
3396
- "from": {
3397
- "type": "string (UTC date, inclusive — the dateWorked window start: the from-day start) — opts.tz never shifts it (dateWorked is date-only)"
3398
- },
3399
- "to": {
3400
- "type": "string (UTC date, inclusive — the dateWorked window end: the to-day end) — opts.tz never shifts it (dateWorked is date-only). One bounded dateWorked read (max 500 entries) plus at most 2 billing_items joins (250 ids each, every read entry); the window is ON the dateWorked field (the posted narrowing is a client-side filter after the join). Result { items, postedCount, unpostedCount, window, nextCursor, unconfirmed, truncated } — the counts cover the returned rows only (at most limit); opts.cursor pages past 500 entries, opts.countOnly returns whole-window totals { window, total, postedCount, unpostedCount, vendorCalls, estimate, budget } under opts.maxVendorCalls (default 30, max 200; BUDGET_EXCEEDED refusal); truncated is true when the entry read has a live next-page cursor (more matching rows exist) or a join page was truncated"
3401
- }
3402
- },
3403
- "required": [
3404
- "from",
3405
- "to"
3406
- ]
3407
- },
3408
- "annotations": {
3409
- "readOnlyHint": true,
3410
- "destructiveHint": false,
3411
- "idempotentHint": true,
3412
- "openWorldHint": true
3413
- },
3414
- "purpose": "Get time entries WORKED within an inclusive dateWorked window with the posted/unposted split. The window is ON the dateWorked field — the name's \"posted\" is the posted/unposted SPLIT derived from the billing_items.timeEntryID join (an entry a billing item references is posted, approval is not consulted), never a posted-date window or a postedDate read. The counts cover the returned rows only; truncated flags a capped read. time_entries.dateWorkedWindow is the unambiguous name.",
3415
- "bounds": ""
3416
- },
3417
- {
3418
- "op": "time_entries.query",
3419
- "verb": "query",
3420
- "effect": "read",
3421
- "kind": "primitive",
3422
- "flags": [],
3423
- "requiredArgs": [],
3424
- "input": {
3425
- "properties": {
3426
- "filter": {
3427
- "type": "FilterCondition[] (13 operators + and/or; autotask_describe for the exact grammar)"
3428
- },
3429
- "limit": {
3430
- "type": "number",
3431
- "default": 500,
3432
- "max": 2500,
3433
- "note": "TOTAL records (server-side pagination; not a page size)"
3434
- },
3435
- "includeFields": {
3436
- "type": "string[]",
3437
- "note": "field subset to return (vendor API casing; omit = full model — pass to save context)"
3438
- }
3439
- },
3440
- "required": []
3441
- },
3442
- "annotations": {
3443
- "readOnlyHint": true,
3444
- "destructiveHint": false,
3445
- "idempotentHint": true,
3446
- "openWorldHint": true
3447
- },
3448
- "purpose": "Query TimeEntry records with a JSON filter.",
3449
- "bounds": "Bounded: server-side pagination — ordinary limit default 500, max 2500; walk.maxRows independently sets the total cap, with pages at most min(500, limit); 1000-page guard; hasMore/truncated flag remaining rows."
3450
- },
3451
- {
3452
- "op": "time_entries.resolve",
3453
- "verb": "resolve",
3454
- "effect": "read",
3455
- "kind": "helper",
3456
- "flags": [],
3457
- "requiredArgs": [
3458
- "identifier"
3459
- ],
3460
- "input": {
3461
- "properties": {
3462
- "identifier": {
3463
- "type": "number | string"
3464
- }
3465
- },
3466
- "required": [
3467
- "identifier"
3468
- ]
3469
- },
3470
- "annotations": {
3471
- "readOnlyHint": true,
3472
- "destructiveHint": false,
3473
- "idempotentHint": true,
3474
- "openWorldHint": true
3475
- },
3476
- "purpose": "Resolve a time entry from a numeric id (the direct fetch) only — this resource has no searchable text field, so a non-numeric identifier throws a config error.",
3477
- "bounds": ""
3478
- },
3479
- {
3480
- "op": "time_entries.today",
3481
- "verb": "today",
3482
- "effect": "read",
3483
- "kind": "helper",
3484
- "flags": [],
3485
- "requiredArgs": [],
3486
- "input": {
3487
- "properties": {},
3488
- "required": []
3489
- },
3490
- "annotations": {
3491
- "readOnlyHint": true,
3492
- "destructiveHint": false,
3493
- "idempotentHint": true,
3494
- "openWorldHint": true
3495
- },
3496
- "purpose": "Get time entries for today (dateWorked = now, server date).",
3497
- "bounds": ""
3498
- },
3499
- {
3500
- "op": "time_entries.yesterday",
3501
- "verb": "yesterday",
3502
- "effect": "read",
3503
- "kind": "helper",
3504
- "flags": [],
3505
- "requiredArgs": [],
3506
- "input": {
3507
- "properties": {},
3508
- "required": []
3509
- },
3510
- "annotations": {
3511
- "readOnlyHint": true,
3512
- "destructiveHint": false,
3513
- "idempotentHint": true,
3514
- "openWorldHint": true
3515
- },
3516
- "purpose": "Get time entries for yesterday (dateWorked = yesterday, server date).",
3059
+ "purpose": "Resolve a time entry from a numeric id (the direct fetch) only — this resource has no searchable text field, so a non-numeric identifier throws a config error.",
3517
3060
  "bounds": ""
3518
3061
  }
3519
3062
  ],
3520
3063
  "backingOperations": [
3521
3064
  "time_entries.count",
3522
- "time_entries.customRange",
3523
3065
  "time_entries.dateWorkedWindow",
3524
3066
  "time_entries.get",
3525
3067
  "time_entries.getEntityInfo",
3526
3068
  "time_entries.getFieldInfo",
3527
- "time_entries.getPosted",
3528
3069
  "time_entries.getUdfInfo",
3529
- "time_entries.getUnposted",
3530
- "time_entries.last14",
3531
- "time_entries.last24h",
3532
- "time_entries.last30",
3533
- "time_entries.last7",
3534
- "time_entries.last90",
3535
- "time_entries.lastFullFortnight",
3536
- "time_entries.lastFullMonth",
3537
- "time_entries.lastFullQuarter",
3538
- "time_entries.lastFullWeek",
3539
- "time_entries.postedWithin",
3540
3070
  "time_entries.query",
3541
- "time_entries.resolve",
3542
- "time_entries.today",
3543
- "time_entries.yesterday"
3071
+ "time_entries.resolve"
3544
3072
  ]
3545
3073
  },
3546
3074
  {
@@ -3549,7 +3077,7 @@
3549
3077
  "entity": "timeEntry",
3550
3078
  "apiEntity": "time_entries",
3551
3079
  "effectLine": "effect=write (plus destructive only when curation re-includes it)",
3552
- "description": "Write timeEntry records (mode: write — the registry operations with effect=write for time_entries: approve, create, createForResource, createIfNotExists, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: approve [id]; create [data]; createForResource [resourceID, params]; createIfNotExists [data]; update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
3080
+ "description": "Write timeEntry records (mode: write — the registry operations with effect=write for time_entries: create, update). Writes are dry-run first: dry_run defaults to true — the request is validated against the registry inputSchema and a DryRunResult envelope is returned; the mutating request is never issued unless the caller passes confirm (the n8n-side handler must force dry-run unless confirm is present, mirroring the SDK governor). Destructive operations are refused without the confirm flag — confirm must equal the operation key exactly (the deliberate act is the call). The n8n consumer surface excludes destructive operations by curation default (excludeDestructive); they remain available on the canonical SDK server (autotask_delete / per-op tools). By curation default (excludeDestructive) no destructive operation is included; destructive capability remains on the canonical SDK server surface. Per-operation required arguments: create [data]; update [id, data]. Errors carry the SDK error contract: code, category, retryable, operation, httpStatus, suggestedAction (the nextAction directive — follow the directive), correlationId, vendorError (when the vendor returned a response body — the deep-redacted body, host-sourced text wrapped as untrusted content when untrusted-wrapping is on); credentials never appear in an error message.",
3553
3081
  "annotations": {
3554
3082
  "readOnlyHint": false,
3555
3083
  "destructiveHint": false,
@@ -3562,26 +3090,17 @@
3562
3090
  "operation": {
3563
3091
  "type": "string",
3564
3092
  "enum": [
3565
- "approve",
3566
3093
  "create",
3567
- "createForResource",
3568
- "createIfNotExists",
3569
3094
  "update"
3570
3095
  ]
3571
3096
  },
3572
- "id": {
3573
- "type": "number | string"
3574
- },
3575
3097
  "data": {
3576
3098
  "type": "object",
3577
3099
  "ref": "time_entries body (see types)"
3578
3100
  },
3579
- "resourceID": {
3101
+ "id": {
3580
3102
  "type": "number | string"
3581
3103
  },
3582
- "params": {
3583
- "type": "object — the smart-create params: { internallAllocationCodeID?: number (pin the internal linkage code), ticketID?: number, taskID?: number, roleID?: number (pin the task/ticket roleID — vendor-validated as an allocation code), dateWorked?: UTC YYYY-MM-DD, startDateTime?: UTC ISO, endDateTime?: UTC ISO, hoursWorked?: number, + any other vendor TimeEntry field } (exactly one linkage pin; none → the default internal linkage with the node-resolved code)"
3584
- },
3585
3104
  "dry_run": {
3586
3105
  "type": "boolean",
3587
3106
  "default": true,
@@ -3597,35 +3116,6 @@
3597
3116
  ]
3598
3117
  },
3599
3118
  "operations": [
3600
- {
3601
- "op": "time_entries.approve",
3602
- "verb": "approve",
3603
- "effect": "write",
3604
- "kind": "helper",
3605
- "flags": [],
3606
- "requiredArgs": [
3607
- "id"
3608
- ],
3609
- "input": {
3610
- "properties": {
3611
- "id": {
3612
- "type": "number | string"
3613
- }
3614
- },
3615
- "required": [
3616
- "id"
3617
- ]
3618
- },
3619
- "annotations": {
3620
- "readOnlyHint": false,
3621
- "destructiveHint": false,
3622
- "idempotentHint": false,
3623
- "openWorldHint": true
3624
- },
3625
- "purpose": "Approve a time entry (the REST-only approval path): create the BillingItemApprovalLevels record, apply the vendor-documented follow-up update of BillingApprovalDateTime + BillingApprovalResourceID, then read the entry back and report whether the vendor accepted it.",
3626
- "bounds": "Dry-run first (dry_run).",
3627
- "dryRun": true
3628
- },
3629
3119
  {
3630
3120
  "op": "time_entries.create",
3631
3121
  "verb": "create",
@@ -3656,70 +3146,6 @@
3656
3146
  "bounds": "Dry-run first (dry_run).",
3657
3147
  "dryRun": true
3658
3148
  },
3659
- {
3660
- "op": "time_entries.createForResource",
3661
- "verb": "createForResource",
3662
- "effect": "write",
3663
- "kind": "helper",
3664
- "flags": [],
3665
- "requiredArgs": [
3666
- "resourceID",
3667
- "params"
3668
- ],
3669
- "input": {
3670
- "properties": {
3671
- "resourceID": {
3672
- "type": "number | string"
3673
- },
3674
- "params": {
3675
- "type": "object — the smart-create params: { internallAllocationCodeID?: number (pin the internal linkage code), ticketID?: number, taskID?: number, roleID?: number (pin the task/ticket roleID — vendor-validated as an allocation code), dateWorked?: UTC YYYY-MM-DD, startDateTime?: UTC ISO, endDateTime?: UTC ISO, hoursWorked?: number, + any other vendor TimeEntry field } (exactly one linkage pin; none → the default internal linkage with the node-resolved code)"
3676
- }
3677
- },
3678
- "required": [
3679
- "resourceID",
3680
- "params"
3681
- ]
3682
- },
3683
- "annotations": {
3684
- "readOnlyHint": false,
3685
- "destructiveHint": false,
3686
- "idempotentHint": false,
3687
- "openWorldHint": true
3688
- },
3689
- "purpose": "Create a time entry for a resource with the allocation-code dependency resolved by the node: it verifies the resource is active, honours the caller's linkage pin (else the default internal linkage), resolves an EXISTING active allocation code applicable to the resource (never creates one — the API user cannot), and posts the vendor-valid body.",
3690
- "bounds": "Dry-run first (dry_run).",
3691
- "dryRun": true
3692
- },
3693
- {
3694
- "op": "time_entries.createIfNotExists",
3695
- "verb": "createIfNotExists",
3696
- "effect": "write",
3697
- "kind": "helper",
3698
- "flags": [],
3699
- "requiredArgs": [
3700
- "data"
3701
- ],
3702
- "input": {
3703
- "properties": {
3704
- "data": {
3705
- "type": "object",
3706
- "ref": "time_entries body (see types)"
3707
- }
3708
- },
3709
- "required": [
3710
- "data"
3711
- ]
3712
- },
3713
- "annotations": {
3714
- "readOnlyHint": false,
3715
- "destructiveHint": false,
3716
- "idempotentHint": false,
3717
- "openWorldHint": true
3718
- },
3719
- "purpose": "Create a TimeEntry.",
3720
- "bounds": "Dry-run first (dry_run).",
3721
- "dryRun": true
3722
- },
3723
3149
  {
3724
3150
  "op": "time_entries.update",
3725
3151
  "verb": "update",
@@ -3759,10 +3185,7 @@
3759
3185
  }
3760
3186
  ],
3761
3187
  "backingOperations": [
3762
- "time_entries.approve",
3763
3188
  "time_entries.create",
3764
- "time_entries.createForResource",
3765
- "time_entries.createIfNotExists",
3766
3189
  "time_entries.update"
3767
3190
  ]
3768
3191
  }
@@ -3843,17 +3266,17 @@
3843
3266
  }
3844
3267
  ],
3845
3268
  "stats": {
3846
- "registryRecords": 1282,
3269
+ "registryRecords": 1224,
3847
3270
  "curatedEntities": 7,
3848
3271
  "includedEntities": 7,
3849
3272
  "readTools": 7,
3850
3273
  "writeTools": 7,
3851
3274
  "totalTools": 14,
3852
- "readOperations": 88,
3853
- "writeOperations": 22,
3854
- "approvalGatedWrites": 2,
3275
+ "readOperations": 75,
3276
+ "writeOperations": 15,
3277
+ "approvalGatedWrites": 1,
3855
3278
  "destructiveExcluded": 4
3856
3279
  },
3857
- "capabilitiesHash": "7cf0032e0744a559430781aeafbd3984b708e71f04b850011e08fbf113eaadcc",
3858
- "curationHash": "e19117ce73e38d9aded3be7e7f077a4e185d2e10294622b76c17d67f63cf9e3b"
3280
+ "capabilitiesHash": "729ea0d47099fd45e1dde837510a92bd566a793368d0a4882e2613cbe05f7594",
3281
+ "curationHash": "623fbca25fa416f983dde22c8b736ce813a2db1bd8c188f7124abba710f52452"
3859
3282
  }