From 5abbc1b1c10a92b9d0b95ae8a9f4df4d56deb850 Mon Sep 17 00:00:00 2001 From: ariel-greenfeld Date: Mon, 24 Aug 2026 18:52:52 +0300 Subject: [PATCH 1/3] =?UTF-8?q?BE-1650:=20histories=20catalog=20=E2=80=94?= =?UTF-8?q?=20add=20page/per=5Fpage,=2031-day=20window,=20current-status?= =?UTF-8?q?=20semantics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows xyte-io/server#9302: page/per_page were absent from the getHistories catalog entry (agents could not paginate), and the canonical endpoints.md example used from=0, which now 422s (window capped at 31 days, to >= from). skills/ data copy synced byte-identical via scripts/sync_skills_data.mjs; vitest not run locally (no node_modules, node v20 < required 22). Co-Authored-By: Claude Fable 5 --- skills/xyte-cli/data/public-endpoints.json | 9 +++++++-- skills/xyte-cli/references/endpoints.md | 12 ++++++++---- src/api-catalog/public-endpoints.json | 9 +++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/skills/xyte-cli/data/public-endpoints.json b/skills/xyte-cli/data/public-endpoints.json index ea946be..09dcf4d 100644 --- a/skills/xyte-cli/data/public-endpoints.json +++ b/skills/xyte-cli/data/public-endpoints.json @@ -342,7 +342,9 @@ "to", "device_id", "space_id", - "name" + "name", + "page", + "per_page" ], "authScope": "organization", "bodyType": "none", @@ -351,7 +353,10 @@ "notes": [ "Docs page: https://docs.xyte.io/reference/get-histories", "Docs category: /branches/2.0/categories/reference/API Access", - "Docs parent: /branches/2.0/reference/devices-2" + "Docs parent: /branches/2.0/reference/devices-2", + "from/to are Unix epoch seconds; the window between them must not exceed 31 days and to must be >= from (422 otherwise). from defaults to 1 week ago, to defaults to now.", + "Paginate with page (starts at 1) and per_page (default 100, max 1000). Response includes has_next_page; no total count is returned.", + "status filters by the device's CURRENT status (online, offline, unavailable, error), not the status recorded in each history row." ] }, { diff --git a/skills/xyte-cli/references/endpoints.md b/skills/xyte-cli/references/endpoints.md index cdde7fb..c36f997 100644 --- a/skills/xyte-cli/references/endpoints.md +++ b/skills/xyte-cli/references/endpoints.md @@ -43,7 +43,7 @@ Derived from the bundled public endpoint spec. | --- | --- | --- | --- | | `organization.spaces.getSpaces` | `id`, `name`, `parent_id`, `space_type`, `created_before`, `created_after`, `path_includes` | none | Main listing endpoint with server-side filtering | | `organization.devices.getDevices` | `page`, `per_page`, `space_id` | `page`, `per_page` | Filter devices by one space; shared docs mention `has_next_page`, live Verve/Playground responses returned `next_page`; handle either continuation field | -| `organization.devices.getHistories` | `status`, `from`, `to`, `device_id`, `space_id`, `name` | none | Filtered history lookup; can be time-windowed | +| `organization.devices.getHistories` | `status`, `from`, `to`, `device_id`, `space_id`, `name`, `page`, `per_page` | `page`, `per_page` | Filtered history lookup. `from`/`to` window must not exceed 31 days (422 otherwise); defaults are last week. `status` filters the device's *current* status. | | `organization.commands.getCommands` | `status`, `page`, `per_page` | `page`, `per_page` | Command history pagination and status filter | | `organization.incidents.getIncidents` | `from`, `to`, `status`, `priority`, `title`, `description`, `issue`, `device_model`, `partner_name`, `sub_model`, `space_id`, `page`, `per_page` | `page`, `per_page` | Incident filtering matrix. Use integer `from` and `to`; for reliable active-incident fetches use both (`from=0`, `to=`). | | `organization.notes.getAllDeviceNotes` | `page`, `per_page` | `page`, `per_page` | Paginated notes across all devices | @@ -78,12 +78,16 @@ xyte-cli api call organization.devices.getHistories \ --tenant \ --query-json '{ "status": "online", - "from": 0, - "to": 2000000000, - "space_id": "" + "from": , + "to": , + "space_id": "", + "page": 1, + "per_page": 100 }' ``` +The `from`/`to` window may not exceed 31 days (the API returns 422 for wider or reversed windows). Walk further back by sliding the window; walk within a window with `page` until `has_next_page` is false. + ### `organization.incidents.getIncidents` ```bash diff --git a/src/api-catalog/public-endpoints.json b/src/api-catalog/public-endpoints.json index ea946be..09dcf4d 100644 --- a/src/api-catalog/public-endpoints.json +++ b/src/api-catalog/public-endpoints.json @@ -342,7 +342,9 @@ "to", "device_id", "space_id", - "name" + "name", + "page", + "per_page" ], "authScope": "organization", "bodyType": "none", @@ -351,7 +353,10 @@ "notes": [ "Docs page: https://docs.xyte.io/reference/get-histories", "Docs category: /branches/2.0/categories/reference/API Access", - "Docs parent: /branches/2.0/reference/devices-2" + "Docs parent: /branches/2.0/reference/devices-2", + "from/to are Unix epoch seconds; the window between them must not exceed 31 days and to must be >= from (422 otherwise). from defaults to 1 week ago, to defaults to now.", + "Paginate with page (starts at 1) and per_page (default 100, max 1000). Response includes has_next_page; no total count is returned.", + "status filters by the device's CURRENT status (online, offline, unavailable, error), not the status recorded in each history row." ] }, { From 569b820399ffde63a2056b33749c47926caf3ba9 Mon Sep 17 00:00:00 2001 From: ariel-greenfeld Date: Wed, 2 Sep 2026 15:27:06 +0300 Subject: [PATCH 2/3] BE-1650: address PR #73 review feedback - Catalog notes: inclusive from/to bounds, independent defaults (send both), 422-not-clamped for page/per_page, stable newest-first ordering, and deep-page-walk warning; skill data re-synced byte-identical. - endpoints.md: concrete runnable timestamps in the getHistories example, replace-with-current-epoch guidance, to = previous from - 1 sliding step, prefer-narrow-window guidance. - tests: pin new getHistories queryParams and notes (31 days, has_next_page) and the endpoints.md guidance prose. - CHANGELOG: Unreleased entry + skills refresh upgrade note. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 6 ++++++ node_modules | 1 - skills/xyte-cli/data/public-endpoints.json | 5 +++-- skills/xyte-cli/references/endpoints.md | 6 +++--- src/api-catalog/public-endpoints.json | 5 +++-- tests/endpoints.test.ts | 13 ++++++++++++- tests/skills-notes-guidance.test.ts | 8 ++++++++ 7 files changed, 35 insertions(+), 9 deletions(-) delete mode 120000 node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 973b170..097e764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is inspired by Keep a Changelog and this project follows SemVer for ` ## [Unreleased] +### Changed +- `organization.devices.getHistories` catalog notes and skill docs now document `page`/`per_page` pagination (422 outside 1..1000, not clamped), the 31-day inclusive `from`/`to` window (422 when exceeded or reversed), the stable newest-first ordering, and guidance to prefer narrow windows and filters over deep page walks. + +### Upgrade notes +- Existing workspaces that already installed the shipped skill bundle should run `xyte-cli skills refresh` after upgrading — the previous `getHistories` example used a wide `from=0` window that now returns 422 against production. + ## [0.14.0] - 2026-08-03 ### Added diff --git a/node_modules b/node_modules deleted file mode 120000 index ab44d50..0000000 --- a/node_modules +++ /dev/null @@ -1 +0,0 @@ -../xyte-cli/node_modules \ No newline at end of file diff --git a/skills/xyte-cli/data/public-endpoints.json b/skills/xyte-cli/data/public-endpoints.json index 09dcf4d..7ecf941 100644 --- a/skills/xyte-cli/data/public-endpoints.json +++ b/skills/xyte-cli/data/public-endpoints.json @@ -354,8 +354,9 @@ "Docs page: https://docs.xyte.io/reference/get-histories", "Docs category: /branches/2.0/categories/reference/API Access", "Docs parent: /branches/2.0/reference/devices-2", - "from/to are Unix epoch seconds; the window between them must not exceed 31 days and to must be >= from (422 otherwise). from defaults to 1 week ago, to defaults to now.", - "Paginate with page (starts at 1) and per_page (default 100, max 1000). Response includes has_next_page; no total count is returned.", + "from/to are Unix epoch seconds and both bounds are INCLUSIVE; the window between them must not exceed 31 days and to must be >= from (422 otherwise). Defaults (from: 1 week ago, to: now) apply independently, so send both bounds - a lone to older than a week 422s against the default from.", + "Paginate with page (starts at 1) and per_page (default 100). Values outside 1..1000 for per_page, or page < 1, are rejected with 422 - not clamped. Response includes has_next_page; no total count is returned.", + "Rows are ordered newest-first (created_at desc, id desc - stable). Slide the window back with to = previous_from - 1 (bounds are inclusive) to avoid duplicate boundary rows. Prefer a narrower window or a device_id/space_id filter over deep page walks - each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window.", "status filters by the device's CURRENT status (online, offline, unavailable, error), not the status recorded in each history row." ] }, diff --git a/skills/xyte-cli/references/endpoints.md b/skills/xyte-cli/references/endpoints.md index c36f997..0ed8bf4 100644 --- a/skills/xyte-cli/references/endpoints.md +++ b/skills/xyte-cli/references/endpoints.md @@ -78,15 +78,15 @@ xyte-cli api call organization.devices.getHistories \ --tenant \ --query-json '{ "status": "online", - "from": , - "to": , + "from": 1707400000, + "to": 1710000000, "space_id": "", "page": 1, "per_page": 100 }' ``` -The `from`/`to` window may not exceed 31 days (the API returns 422 for wider or reversed windows). Walk further back by sliding the window; walk within a window with `page` until `has_next_page` is false. +Replace `1710000000` with the current Unix timestamp and `1707400000` with a value at most 31 days earlier; the `from`/`to` window may not exceed 31 days (the API returns 422 for wider or reversed windows). Both bounds are inclusive: walk further back by sliding the window with `to = previous from - 1`, and walk within a window with `page` until `has_next_page` is false. Prefer a shorter window or a `device_id`/`space_id` filter over deep page walks — each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window. ### `organization.incidents.getIncidents` diff --git a/src/api-catalog/public-endpoints.json b/src/api-catalog/public-endpoints.json index 09dcf4d..7ecf941 100644 --- a/src/api-catalog/public-endpoints.json +++ b/src/api-catalog/public-endpoints.json @@ -354,8 +354,9 @@ "Docs page: https://docs.xyte.io/reference/get-histories", "Docs category: /branches/2.0/categories/reference/API Access", "Docs parent: /branches/2.0/reference/devices-2", - "from/to are Unix epoch seconds; the window between them must not exceed 31 days and to must be >= from (422 otherwise). from defaults to 1 week ago, to defaults to now.", - "Paginate with page (starts at 1) and per_page (default 100, max 1000). Response includes has_next_page; no total count is returned.", + "from/to are Unix epoch seconds and both bounds are INCLUSIVE; the window between them must not exceed 31 days and to must be >= from (422 otherwise). Defaults (from: 1 week ago, to: now) apply independently, so send both bounds - a lone to older than a week 422s against the default from.", + "Paginate with page (starts at 1) and per_page (default 100). Values outside 1..1000 for per_page, or page < 1, are rejected with 422 - not clamped. Response includes has_next_page; no total count is returned.", + "Rows are ordered newest-first (created_at desc, id desc - stable). Slide the window back with to = previous_from - 1 (bounds are inclusive) to avoid duplicate boundary rows. Prefer a narrower window or a device_id/space_id filter over deep page walks - each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window.", "status filters by the device's CURRENT status (online, offline, unavailable, error), not the status recorded in each history row." ] }, diff --git a/tests/endpoints.test.ts b/tests/endpoints.test.ts index 7312b78..1c3b245 100644 --- a/tests/endpoints.test.ts +++ b/tests/endpoints.test.ts @@ -148,7 +148,18 @@ describe('public endpoint catalog', () => { expect(getDevices?.notes?.join(' ')).toContain('next_page'); const getHistories = endpoints.find((item) => item.key === 'organization.devices.getHistories'); - expect(getHistories?.queryParams).toEqual(['status', 'from', 'to', 'device_id', 'space_id', 'name']); + expect(getHistories?.queryParams).toEqual([ + 'status', + 'from', + 'to', + 'device_id', + 'space_id', + 'name', + 'page', + 'per_page' + ]); + expect(getHistories?.notes?.join(' ')).toContain('31 days'); + expect(getHistories?.notes?.join(' ')).toContain('has_next_page'); const getIncidents = endpoints.find((item) => item.key === 'organization.incidents.getIncidents'); expect(getIncidents?.queryParams).toEqual([ diff --git a/tests/skills-notes-guidance.test.ts b/tests/skills-notes-guidance.test.ts index 86e8c65..a0e893a 100644 --- a/tests/skills-notes-guidance.test.ts +++ b/tests/skills-notes-guidance.test.ts @@ -29,6 +29,14 @@ describe('notes endpoint docs and skill guidance', () => { expect(content).toContain('created_by: null'); }); + it('pins getHistories window and pagination guidance in the shipped endpoint reference', () => { + const content = read('skills/xyte-cli/references/endpoints.md'); + + expect(content).toContain('31 days'); + expect(content).toContain('has_next_page'); + expect(content).toContain('to = previous from - 1'); + }); + it('documents generic utility behavior for note write endpoints', () => { const docs = read('docs/ai-utility-preprocessing.md'); const skill = read('skills/xyte-cli/references/ai-utility-preprocessing.md'); From 46567c1bd99b7367e2c9ac5a3613e30ff8a859b9 Mon Sep 17 00:00:00 2001 From: ariel-greenfeld Date: Thu, 10 Sep 2026 17:14:38 +0300 Subject: [PATCH 3/3] BE-1650: fix the histories sliding-window recipe per review - slide with to = current window from (not from - 1), dedupe boundary rows on (uuid, create_at) - drop the id desc tiebreaker from the ordering note (id is not in the response) - document the response shape (items, uuid, create_at ISO8601, no row id) - independent from/to defaults wording and per_page 1..1000 rule in endpoints.md - pin the corrected recipe in skills-notes-guidance.test.ts - add a getHistories drift-overrides entry Co-Authored-By: Claude Fable 5.1 --- skills/xyte-cli/data/public-endpoints.json | 3 ++- skills/xyte-cli/references/endpoints.md | 4 ++-- src/api-catalog/drift-overrides.json | 6 ++++++ src/api-catalog/public-endpoints.json | 3 ++- tests/skills-notes-guidance.test.ts | 3 ++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/skills/xyte-cli/data/public-endpoints.json b/skills/xyte-cli/data/public-endpoints.json index 7ecf941..ae833b1 100644 --- a/skills/xyte-cli/data/public-endpoints.json +++ b/skills/xyte-cli/data/public-endpoints.json @@ -356,7 +356,8 @@ "Docs parent: /branches/2.0/reference/devices-2", "from/to are Unix epoch seconds and both bounds are INCLUSIVE; the window between them must not exceed 31 days and to must be >= from (422 otherwise). Defaults (from: 1 week ago, to: now) apply independently, so send both bounds - a lone to older than a week 422s against the default from.", "Paginate with page (starts at 1) and per_page (default 100). Values outside 1..1000 for per_page, or page < 1, are rejected with 422 - not clamped. Response includes has_next_page; no total count is returned.", - "Rows are ordered newest-first (created_at desc, id desc - stable). Slide the window back with to = previous_from - 1 (bounds are inclusive) to avoid duplicate boundary rows. Prefer a narrower window or a device_id/space_id filter over deep page walks - each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window.", + "Rows are ordered newest-first (created_at desc, stable). To read further back, set the next window's to to the current window's from - not from - 1. Both bounds are inclusive, so a row landing exactly on the boundary is returned in both windows; drop rows whose (uuid, create_at) you have already seen. Prefer a narrower window or a device_id/space_id filter over deep page walks - each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window.", + "Response shape: rows are under items; each row has uuid (the device id), create_at (sic - ISO8601 string, not epoch seconds), name, space_id, model, partner, state. No row id is returned.", "status filters by the device's CURRENT status (online, offline, unavailable, error), not the status recorded in each history row." ] }, diff --git a/skills/xyte-cli/references/endpoints.md b/skills/xyte-cli/references/endpoints.md index 0ed8bf4..efc1d0e 100644 --- a/skills/xyte-cli/references/endpoints.md +++ b/skills/xyte-cli/references/endpoints.md @@ -43,7 +43,7 @@ Derived from the bundled public endpoint spec. | --- | --- | --- | --- | | `organization.spaces.getSpaces` | `id`, `name`, `parent_id`, `space_type`, `created_before`, `created_after`, `path_includes` | none | Main listing endpoint with server-side filtering | | `organization.devices.getDevices` | `page`, `per_page`, `space_id` | `page`, `per_page` | Filter devices by one space; shared docs mention `has_next_page`, live Verve/Playground responses returned `next_page`; handle either continuation field | -| `organization.devices.getHistories` | `status`, `from`, `to`, `device_id`, `space_id`, `name`, `page`, `per_page` | `page`, `per_page` | Filtered history lookup. `from`/`to` window must not exceed 31 days (422 otherwise); defaults are last week. `status` filters the device's *current* status. | +| `organization.devices.getHistories` | `status`, `from`, `to`, `device_id`, `space_id`, `name`, `page`, `per_page` | `page`, `per_page` | Filtered history lookup. `from`/`to` window must not exceed 31 days (422 otherwise); defaults: `from` = 1 week ago, `to` = now, applied independently — send both. `status` filters the device's *current* status. | | `organization.commands.getCommands` | `status`, `page`, `per_page` | `page`, `per_page` | Command history pagination and status filter | | `organization.incidents.getIncidents` | `from`, `to`, `status`, `priority`, `title`, `description`, `issue`, `device_model`, `partner_name`, `sub_model`, `space_id`, `page`, `per_page` | `page`, `per_page` | Incident filtering matrix. Use integer `from` and `to`; for reliable active-incident fetches use both (`from=0`, `to=`). | | `organization.notes.getAllDeviceNotes` | `page`, `per_page` | `page`, `per_page` | Paginated notes across all devices | @@ -86,7 +86,7 @@ xyte-cli api call organization.devices.getHistories \ }' ``` -Replace `1710000000` with the current Unix timestamp and `1707400000` with a value at most 31 days earlier; the `from`/`to` window may not exceed 31 days (the API returns 422 for wider or reversed windows). Both bounds are inclusive: walk further back by sliding the window with `to = previous from - 1`, and walk within a window with `page` until `has_next_page` is false. Prefer a shorter window or a `device_id`/`space_id` filter over deep page walks — each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window. +Replace `1710000000` with the current Unix timestamp and `1707400000` with a value at most 31 days earlier; the `from`/`to` window may not exceed 31 days (the API returns 422 for wider or reversed windows). Walk within a window with `page` (starts at 1) and `per_page` (default 100, 1..1000 — values outside that range are rejected with 422, not clamped) until `has_next_page` is false. To read further back, set the next window's `to` to the current window's `from` — not `from - 1`. Both bounds are inclusive, so a row landing exactly on the boundary is returned in both windows; drop rows whose `(uuid, create_at)` you have already seen. Rows are under `items`, newest-first; each has `uuid` (the device id) and `create_at` (sic — an ISO8601 string, not epoch seconds), and no row `id`. Prefer a shorter window or a `device_id`/`space_id` filter over deep page walks — each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window. ### `organization.incidents.getIncidents` diff --git a/src/api-catalog/drift-overrides.json b/src/api-catalog/drift-overrides.json index a7c435d..b9bde8c 100644 --- a/src/api-catalog/drift-overrides.json +++ b/src/api-catalog/drift-overrides.json @@ -1,4 +1,10 @@ { + "organization.devices.getHistories": { + "queryParams": ["status", "from", "to", "device_id", "space_id", "name", "page", "per_page"], + "appendNotes": [ + "Public docs omit page/per_page and the 31-day from/to cap; both verified against hub histories_controller.rb (BE-1650, xyte-io/server#9302). Keep the getHistories window, pagination, ordering and response-shape notes in public-endpoints.json when regenerating." + ] + }, "organization.commands.getCommands": { "pathTemplate": "/core/v1/organization/devices/:device_id/commands", "pathParams": [ diff --git a/src/api-catalog/public-endpoints.json b/src/api-catalog/public-endpoints.json index 7ecf941..ae833b1 100644 --- a/src/api-catalog/public-endpoints.json +++ b/src/api-catalog/public-endpoints.json @@ -356,7 +356,8 @@ "Docs parent: /branches/2.0/reference/devices-2", "from/to are Unix epoch seconds and both bounds are INCLUSIVE; the window between them must not exceed 31 days and to must be >= from (422 otherwise). Defaults (from: 1 week ago, to: now) apply independently, so send both bounds - a lone to older than a week 422s against the default from.", "Paginate with page (starts at 1) and per_page (default 100). Values outside 1..1000 for per_page, or page < 1, are rejected with 422 - not clamped. Response includes has_next_page; no total count is returned.", - "Rows are ordered newest-first (created_at desc, id desc - stable). Slide the window back with to = previous_from - 1 (bounds are inclusive) to avoid duplicate boundary rows. Prefer a narrower window or a device_id/space_id filter over deep page walks - each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window.", + "Rows are ordered newest-first (created_at desc, stable). To read further back, set the next window's to to the current window's from - not from - 1. Both bounds are inclusive, so a row landing exactly on the boundary is returned in both windows; drop rows whose (uuid, create_at) you have already seen. Prefer a narrower window or a device_id/space_id filter over deep page walks - each page costs a full OFFSET scan, so high page numbers can time out even inside a legal window.", + "Response shape: rows are under items; each row has uuid (the device id), create_at (sic - ISO8601 string, not epoch seconds), name, space_id, model, partner, state. No row id is returned.", "status filters by the device's CURRENT status (online, offline, unavailable, error), not the status recorded in each history row." ] }, diff --git a/tests/skills-notes-guidance.test.ts b/tests/skills-notes-guidance.test.ts index a0e893a..0822326 100644 --- a/tests/skills-notes-guidance.test.ts +++ b/tests/skills-notes-guidance.test.ts @@ -34,7 +34,8 @@ describe('notes endpoint docs and skill guidance', () => { expect(content).toContain('31 days'); expect(content).toContain('has_next_page'); - expect(content).toContain('to = previous from - 1'); + expect(content).toContain("set the next window's `to` to the current window's `from`"); + expect(content).toContain('(uuid, create_at)'); }); it('documents generic utility behavior for note write endpoints', () => {