Skip to main content

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}/devices in services/amm/agent-web-api/openapi.json#L6997
  • /v2/clients/{client_id}/devices/summary in services/amm/agent-web-api/openapi.json#L7314
  • /v2/clients/{client_id}/devices/{device_id} in services/amm/agent-web-api/openapi.json#L7389
  • /v2/clients/{client_id}/devices/{device_id}/users in services/amm/agent-web-api/openapi.json#L7472
  • /v2/clients/{client_id}/devices/{device_id}/deployments in services/amm/agent-web-api/openapi.json#L7620
  • /v2/clients/{client_id}/devices/{device_id}/deployments/{deployment_id} in services/amm/agent-web-api/openapi.json#L7772
  • DeviceDto in services/amm/agent-web-api/openapi.json#L14536
  • DeviceHardwareDto in services/amm/agent-web-api/openapi.json#L14459
  • DeviceBrowserExtensionDto in services/amm/agent-web-api/openapi.json#L14491
  • DeviceExtensionsDto in services/amm/agent-web-api/openapi.json#L14517
  • DeviceSummaryDto in services/amm/agent-web-api/openapi.json#L14710
  • DeviceUserRefDto in services/amm/agent-web-api/openapi.json#L14754
  • LastActivityDto in services/amm/agent-web-api/openapi.json#L14729
  • DeviceDeploymentRefDto in services/amm/agent-web-api/openapi.json#L14813
  • DeviceDeploymentDto in services/amm/agent-web-api/openapi.json#L14865
  • DeviceStatusEnum in services/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:

FieldDocumented typeOpenAPI type
namestring | nullobject | null
serial_numberstring | nullobject | null
unique_keystring | nullobject | null
os_namestring | nullobject | null
os_versionstring | nullobject | null
last_seen_atstring (ISO 8601) | nullobject | null (with format: date-time)
installed_atstring (ISO 8601) | nullobject | null (with format: date-time)
agent_versionstring | nullobject | null

Affected fields in DeviceHardwareDto:

FieldDocumented typeOpenAPI type
manufacturerstring | nullobject | null
modelstring | nullobject | null
cpu_modelstring | nullobject | null

Affected fields in DeviceBrowserExtensionDto:

FieldDocumented typeOpenAPI type
versionstring | nullobject | null
last_activity_atstring (ISO 8601) | nullobject | null (with format: date-time)

Affected fields in LastActivityDto:

FieldDocumented typeOpenAPI type
atstring (ISO 8601) | nullobject | null (with format: date-time)
desktop_atstring (ISO 8601) | nullobject | null (with format: date-time)
web_atstring (ISO 8601) | nullobject | null (with format: date-time)

Affected fields in DeviceUserRefDto:

FieldDocumented typeOpenAPI type
last_seen_atstring (ISO 8601) | nullobject | null (with format: date-time)

Affected fields in DeviceDeploymentRefDto and DeviceDeploymentDto:

FieldDocumented typeOpenAPI type
enrollment_datestring (ISO 8601) | nullobject | 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 on last_seen_at
  • agent_version -- multi-value exact match filter
  • browser_plugin -- multi-value enum filter (chrome, edge, firefox)
  • browser_plugin_version -- exact match filter
  • manufacturer -- multi-value exact match
  • model -- multi-value exact match
  • cpu_model -- multi-value exact match
  • ram_min / ram_max -- numeric range filter
  • cores_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 archived which 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.

FieldDocumented typeOpenAPI type
hardware.cpu_coresinteger | nullnumber | null
hardware.raminteger | nullnumber | 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 endpointOpenAPI route
List DevicesGET /v2/clients/{client_id}/devices
Get DeviceGET /v2/clients/{client_id}/devices/{device_id}
Device SummaryGET /v2/clients/{client_id}/devices/summary
Device UsersGET /v2/clients/{client_id}/devices/{device_id}/users
List DeploymentsGET /v2/clients/{client_id}/devices/{device_id}/deployments
Get DeploymentGET /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.