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 ea946be..ae833b1 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,12 @@ "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 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, 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 cdde7fb..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` | 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: `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 | @@ -78,12 +78,16 @@ xyte-cli api call organization.devices.getHistories \ --tenant \ --query-json '{ "status": "online", - "from": 0, - "to": 2000000000, - "space_id": "" + "from": 1707400000, + "to": 1710000000, + "space_id": "", + "page": 1, + "per_page": 100 }' ``` +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` ```bash 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 ea946be..ae833b1 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,12 @@ "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 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, 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/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..0822326 100644 --- a/tests/skills-notes-guidance.test.ts +++ b/tests/skills-notes-guidance.test.ts @@ -29,6 +29,15 @@ 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("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', () => { const docs = read('docs/ai-utility-preprocessing.md'); const skill = read('skills/xyte-cli/references/ai-utility-preprocessing.md');