Devices V2 OpenAPI Drift Report
Summary
This report compares the documented v2 devices API against the generated OpenAPI contract in services/amm/agent-web-api/openapi.json.
For this report, docs/api/devices is the source of truth.
Scope Reviewed
/v2/clients/{client_id}/devicesinservices/amm/agent-web-api/openapi.json#L6997/v2/clients/{client_id}/devices/summaryinservices/amm/agent-web-api/openapi.json#L7314/v2/clients/{client_id}/devices/{device_id}inservices/amm/agent-web-api/openapi.json#L7389/v2/clients/{client_id}/devices/{device_id}/usersinservices/amm/agent-web-api/openapi.json#L7472/v2/clients/{client_id}/devices/{device_id}/deploymentsinservices/amm/agent-web-api/openapi.json#L7620/v2/clients/{client_id}/devices/{device_id}/deployments/{deployment_id}inservices/amm/agent-web-api/openapi.json#L7772DeviceDtoinservices/amm/agent-web-api/openapi.json#L14536DeviceHardwareDtoinservices/amm/agent-web-api/openapi.json#L14459DeviceBrowserExtensionDtoinservices/amm/agent-web-api/openapi.json#L14491DeviceExtensionsDtoinservices/amm/agent-web-api/openapi.json#L14517DeviceSummaryDtoinservices/amm/agent-web-api/openapi.json#L14710DeviceUserRefDtoinservices/amm/agent-web-api/openapi.json#L14754LastActivityDtoinservices/amm/agent-web-api/openapi.json#L14729DeviceDeploymentRefDtoinservices/amm/agent-web-api/openapi.json#L14813DeviceDeploymentDtoinservices/amm/agent-web-api/openapi.json#L14865DeviceStatusEnuminservices/amm/agent-web-api/openapi.json#L14450
Findings
1. Systemic type: "object" instead of type: "string" on nullable fields across all DTOs
This is the largest drift in the devices contract. Many nullable string fields are generated as type: "object" instead of type: "string". This affects code generators -- consumers will get Record<string, unknown> instead of string | null.
Affected fields in DeviceDto:
| Field | Documented type | OpenAPI type |
|---|---|---|
name | string | null | object | null |
serial_number | string | null | object | null |
unique_key | string | null | object | null |
os_name | string | null | object | null |
os_version | string | null | object | null |
last_seen_at | string (ISO 8601) | null | object | null (with format: date-time) |
installed_at | string (ISO 8601) | null | object | null (with format: date-time) |
agent_version | string | null | object | null |
Affected fields in DeviceHardwareDto:
| Field | Documented type | OpenAPI type |
|---|---|---|
manufacturer | string | null | object | null |
model | string | null | object | null |
cpu_model | string | null | object | null |
Affected fields in DeviceBrowserExtensionDto:
| Field | Documented type | OpenAPI type |
|---|---|---|
version | string | null | object | null |
last_activity_at | string (ISO 8601) | null | object | null (with format: date-time) |
Affected fields in LastActivityDto:
| Field | Documented type | OpenAPI type |
|---|---|---|
at | string (ISO 8601) | null | object | null (with format: date-time) |
desktop_at | string (ISO 8601) | null | object | null (with format: date-time) |
web_at | string (ISO 8601) | null | object | null (with format: date-time) |
Affected fields in DeviceUserRefDto:
| Field | Documented type | OpenAPI type |
|---|---|---|
last_seen_at | string (ISO 8601) | null | object | null (with format: date-time) |
Affected fields in DeviceDeploymentRefDto and DeviceDeploymentDto:
| Field | Documented type | OpenAPI type |
|---|---|---|
enrollment_date | string (ISO 8601) | null | object | null (with format: date-time) |
Total: 20 fields across 6 DTOs have this drift.
2. GET /v2/clients/{client_id}/devices exposes 13 query parameters that should not be in the public contract
The OpenAPI contract exposes internal filter parameters that are not part of the documented public API. These should be removed from the generated spec to match the docs.
Documented query parameters (correct)
GET /v2/clients/{client_id}/devices
?q=macbook
&user_id=a1d97031-04e2-4907-a249-093f7436207b
&status=active
&os_name=Windows
&sort_by=last_seen_at
&sort_order=desc
&page_size=50
&cursor=...
Parameters in generated OpenAPI that should be removed
username-- search by associated username (partial match)last_seen_from/last_seen_to-- date range filter onlast_seen_atagent_version-- multi-value exact match filterbrowser_plugin-- multi-value enum filter (chrome,edge,firefox)browser_plugin_version-- exact match filtermanufacturer-- multi-value exact matchmodel-- multi-value exact matchcpu_model-- multi-value exact matchram_min/ram_max-- numeric range filtercores_min/cores_max-- numeric range filter
These parameters should be hidden from the OpenAPI spec (e.g. via @ApiExcludeEndpoint on internals, or by removing the @ApiQuery decorators).
3. status parameter type and values differ between docs and OpenAPI
The docs define status as a single string with three values:
status: active | inactive | pending
The OpenAPI defines it as an array with four values:
status: array of (active | inactive | pending | archived)
Differences:
- Type: docs say single string, OpenAPI accepts an array (comma-separated or repeated params)
- Values: OpenAPI includes
archivedwhich is not documented - Default behavior: OpenAPI notes "Omitting this parameter excludes Archived devices by default" -- this filtering behavior is not documented
The DeviceStatusEnum used by DeviceDto.status also includes archived, while the docs only list active, inactive, pending as valid status values.
4. DeviceDeploymentDto is missing the user_id field
The docs define DeviceDeployment with a user_id field:
Documented shape
{
"id": "c2d3e4f5-a6b7-8901-bcde-f12345678901",
"status": "error",
"enrollment_date": "2026-01-29T09:00:00.000Z",
"enrollment_token_id": "e1f2a3b4-c5d6-7890-abcd-ef1234567890",
"user_id": "a1d97031-04e2-4907-a249-093f7436207b",
"error_logs": ["..."],
"created_at": "2026-01-29T09:00:00.000Z",
"updated_at": "2026-01-29T09:00:15.000Z"
}
Current generated contract shape
{
"id": "c2d3e4f5-a6b7-8901-bcde-f12345678901",
"status": "error",
"enrollment_date": {},
"enrollment_token_id": "e1f2a3b4-c5d6-7890-abcd-ef1234567890",
"error_logs": ["..."],
"created_at": "2026-01-29T09:00:00.000Z",
"updated_at": "2026-01-29T09:00:15.000Z"
}
user_id (documented as string (uuid) | null) is completely absent from the DeviceDeploymentDto schema.
5. DeviceHardwareDto integer fields typed as number
The docs define cpu_cores and ram as integer | null:
The OpenAPI types both as number | null. While this is minor, it means code generators will produce number rather than integer, which loses the precision constraint.
| Field | Documented type | OpenAPI type |
|---|---|---|
hardware.cpu_cores | integer | null | number | null |
hardware.ram | integer | null | number | null |
6. GET /deployments/{deployment_id} is missing a 400 response
The docs define shared deployment error responses including 400:
Documented status set
400
401
403
404
500
Current generated status set for GET /deployments/{deployment_id}
401
403
404
500
400 is present on the list deployments route but missing on the detail route.
Notes
Route coverage is fully aligned
All six documented device routes are present in the OpenAPI contract:
| Documented endpoint | OpenAPI route |
|---|---|
| List Devices | GET /v2/clients/{client_id}/devices |
| Get Device | GET /v2/clients/{client_id}/devices/{device_id} |
| Device Summary | GET /v2/clients/{client_id}/devices/summary |
| Device Users | GET /v2/clients/{client_id}/devices/{device_id}/users |
| List Deployments | GET /v2/clients/{client_id}/devices/{device_id}/deployments |
| Get Deployment | GET /v2/clients/{client_id}/devices/{device_id}/deployments/{deployment_id} |
Summary and users endpoints are well-aligned structurally
DeviceSummaryDto fields (total_devices, agent_versions, deployment_errors) match the docs exactly in structure. The drift on summary is limited to the missing 400 response and the nested sub-DTOs not being affected by the object typing issue.
DeviceUserRefDto and DeviceUsersListResponseDto are structurally correct. The only drift is the last_seen_at and LastActivityDto nullable-string-as-object issue from Finding 1.
The object typing issue is likely a NestJS/Swagger decorator problem
The pattern of nullable strings generating as type: "object" is consistent with missing @ApiProperty({ type: String }) annotations on nullable DTO properties. This is the same root cause seen in the apps drift report.
Requested Action
Please update the NestJS DTOs, decorators, and controller response metadata so that the generated OpenAPI matches the documented v2 devices API in docs/api/devices, then regenerate services/amm/agent-web-api/openapi.json and validate the contract.