Skip to main content

Apps V2 OpenAPI Drift Report

Summary

This report compares the documented v2 apps API against the generated OpenAPI contract in services/amm/agent-web-api/openapi.json.

For this report, docs/api/apps is the source of truth.

Scope Reviewed

  • /v2/clients/{client_id}/apps in services/amm/agent-web-api/openapi.json#L4764
  • /v2/clients/{client_id}/apps/{app_id} in services/amm/agent-web-api/openapi.json#L4919
  • /v2/clients/{client_id}/apps/favorites in services/amm/agent-web-api/openapi.json#L5103
  • /v2/clients/{client_id}/apps/favorites/remove in services/amm/agent-web-api/openapi.json#L5188
  • /v2/clients/{client_id}/apps/approvals in services/amm/agent-web-api/openapi.json#L5273
  • /v2/clients/{client_id}/apps/approvals/remove in services/amm/agent-web-api/openapi.json#L5358
  • /v2/clients/{client_id}/apps/{app_id}/history in services/amm/agent-web-api/openapi.json#L5443
  • /v2/clients/{client_id}/apps/{app_id}/history/notes in services/amm/agent-web-api/openapi.json#L5552
  • AppListItemDto in services/amm/agent-web-api/openapi.json#L13209
  • CatalogVendorEmbedDto in services/amm/agent-web-api/openapi.json#L13306
  • CatalogAppEmbedDto in services/amm/agent-web-api/openapi.json#L13343
  • AppDetailDto in services/amm/agent-web-api/openapi.json#L13404
  • BulkUpdateResponseDto in services/amm/agent-web-api/openapi.json#L13476
  • BulkApprovalsResponseDto in services/amm/agent-web-api/openapi.json#L13507
  • AppHistoryEventDto in services/amm/agent-web-api/openapi.json#L13547
  • AppHistoryListResponseDto in services/amm/agent-web-api/openapi.json#L13598

Findings

1. GET /v2/clients/{client_id}/apps returns the wrong DTO shape

The docs define the list response as App[], where each item includes the full embedded catalog_app object plus client-specific fields:

The generated OpenAPI instead returns AppListItemDto, which is a lighter shape with different field names and missing fields:

  • AppsListResponseDto in services/amm/agent-web-api/openapi.json#L13245
  • AppListItemDto in services/amm/agent-web-api/openapi.json#L13209

Documented list item shape

{
"id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"catalog_app": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"canonical_name": "Slack",
"description": "A messaging and collaboration platform for teams",
"display_colour": "#4A154B",
"logo": "https://cdn.example.com/logos/slack.png",
"type": "desktop",
"vendor": {
"id": "f6a7b8c9-d0e1-2345-fabc-678901234567",
"name": "Salesforce, Inc.",
"url": "https://www.salesforce.com"
},
"categories": [
{
"id": "a7b8c9d0-e1f2-3456-abcd-789012345678",
"name": "Communication"
}
],
"created_at": "2025-06-15T10:00:00.000Z",
"updated_at": "2026-02-10T09:15:00.000Z"
},
"is_favorite": true,
"is_approved": true,
"license_count": 25,
"created_at": "2026-01-15T10:00:00.000Z",
"updated_at": "2026-02-20T14:30:00.000Z"
}

Current generated contract shape

{
"id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"app_name": "Slack",
"logo": {},
"categories": [
"Communication"
],
"is_favorite": true,
"is_approved": true
}

This is the largest drift in the apps contract. Consumers built from the OpenAPI spec will not receive the App shape described in the docs.

2. GET /v2/clients/{client_id}/apps query parameters do not match the docs

The docs define these query params for app list:

The generated OpenAPI exposes a different set:

  • /v2/clients/{client_id}/apps in services/amm/agent-web-api/openapi.json#L4764

Documented request shape

GET /v2/clients/{client_id}/apps
?q=slack
&category_ids=a7b8c9d0-e1f2-3456-abcd-789012345678
&vendor_ids=f6a7b8c9-d0e1-2345-fabc-678901234567
&type=desktop
&is_favorite=true
&is_approved=true
&sort_by=canonical_name
&sort_order=asc
&page_size=50
&cursor=...

Current generated request shape

GET /v2/clients/{client_id}/apps
?q=slack
&category_id=a7b8c9d0-e1f2-3456-abcd-789012345678
&is_favorite=true
&is_approved=true
&sort_by=app_name|date_added|created_at
&sort_order=asc|desc
&page_size=50
&cursor=...

Differences:

  • vendor_ids is documented, but missing from OpenAPI
  • category_ids is documented, but OpenAPI exposes category_id instead
  • type is documented, but missing from OpenAPI
  • sort_by values do not match:
    • docs: canonical_name, created_at, updated_at
    • OpenAPI: app_name, date_added, created_at

3. The embedded CatalogApp schema does not match the documented CatalogApp DTO

The apps docs explicitly reuse the documented catalog DTO inside App.catalog_app:

The generated schema for the embedded catalog object does not match that documented shape:

  • CatalogAppEmbedDto in services/amm/agent-web-api/openapi.json#L13343
  • CatalogVendorEmbedDto in services/amm/agent-web-api/openapi.json#L13306
  • AppDetailDto in services/amm/agent-web-api/openapi.json#L13404

Documented embedded shape

{
"catalog_app": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"canonical_name": "Custom Internal Tool",
"description": null,
"display_colour": null,
"logo": null,
"type": null,
"vendor": null,
"categories": [],
"created_at": "2025-08-01T10:00:00.000Z",
"updated_at": "2025-08-01T10:00:00.000Z"
}
}

Current generated contract shape

{
"catalog_app": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"canonical_name": "Custom Internal Tool",
"description": {},
"display_colour": {},
"logo": {},
"type": "desktop",
"vendor": {
"id": "f6a7b8c9-d0e1-2345-fabc-678901234567",
"name": "Salesforce, Inc.",
"url": {}
},
"categories": []
}
}

Drift details:

  • description is documented as string | null, but OpenAPI types it as nullable object
  • display_colour is documented as string | null, but OpenAPI types it as nullable object
  • logo is documented as string | null, but OpenAPI types it as nullable object
  • vendor.url is documented as string | null, but OpenAPI types it as nullable object
  • type is nullable in the docs example, but OpenAPI requires a non-null string

Because AppDetailDto embeds CatalogAppEmbedDto, this drift affects GET /apps/{app_id} and PATCH /apps/{app_id} responses too.

4. Favorites bulk endpoints return the wrong response schema

The docs define favorites bulk operations as returning the same bulk result shape used elsewhere: succeeded plus failed[] with per-ID errors.

The generated OpenAPI instead returns a count-only response:

  • /v2/clients/{client_id}/apps/favorites in services/amm/agent-web-api/openapi.json#L5103
  • /v2/clients/{client_id}/apps/favorites/remove in services/amm/agent-web-api/openapi.json#L5188
  • BulkUpdateResponseDto in services/amm/agent-web-api/openapi.json#L13476

Documented response shape

{
"succeeded": [
"f1a2b3c4-d5e6-7890-abcd-ef1234567890"
],
"failed": [
{
"id": "ffffffff-ffff-ffff-ffff-ffffffffffff",
"error": "App not found"
}
]
}

Current generated contract shape

{
"requested_count": 2,
"updated_count": 1
}

This means the OpenAPI contract for favorites does not expose the per-app success/failure detail that the docs require.

5. Several documented 404 responses are missing from the generated contract

The docs say these endpoints return 404 when the client_id does not resolve:

The generated OpenAPI does not document 404 on those routes:

  • /v2/clients/{client_id}/apps in services/amm/agent-web-api/openapi.json#L4857
  • /v2/clients/{client_id}/apps/favorites in services/amm/agent-web-api/openapi.json#L5126
  • /v2/clients/{client_id}/apps/favorites/remove in services/amm/agent-web-api/openapi.json#L5211
  • /v2/clients/{client_id}/apps/approvals in services/amm/agent-web-api/openapi.json#L5296
  • /v2/clients/{client_id}/apps/approvals/remove in services/amm/agent-web-api/openapi.json#L5381

Documented status set example

200
400
401
403
404
500

Current generated status set example for POST /v2/clients/{client_id}/apps/favorites

200
400
401
403
500

6. AppHistoryEvent field typing is looser than the documented DTO

The history docs define message and comment as strings or null, and they constrain metadata by event_type:

The generated schema weakens those fields:

  • AppHistoryEventDto in services/amm/agent-web-api/openapi.json#L13547

Documented event shape

{
"id": "9f4f8c52-3d85-4d0f-bec3-d95e890d1d24",
"event_type": "approval_status_changed",
"actor": {
"id": "1b8fbc0f-f234-4da7-9cb2-5ae10ef63b8e",
"display_name": "Jane Smith"
},
"message": "approved this app",
"comment": null,
"metadata": {
"is_approved": true
},
"created_at": "2026-03-06T19:42:11.000Z"
}

Current generated contract shape

{
"id": "9f4f8c52-3d85-4d0f-bec3-d95e890d1d24",
"event_type": "approval_status_changed",
"actor": {
"id": "1b8fbc0f-f234-4da7-9cb2-5ae10ef63b8e",
"display_name": "Jane Smith"
},
"message": {},
"comment": {},
"metadata": {
"any_key": "any_value"
},
"created_at": "2026-03-06T19:42:11.000Z"
}

Drift details:

  • message is documented as string | null, but OpenAPI types it as nullable object
  • comment is documented as string | null, but OpenAPI types it as nullable object
  • metadata is documented with event-specific shapes, but OpenAPI exposes only unconstrained object

7. GET /v2/clients/{client_id}/apps/{app_id}/history parameter typing differs from the docs

The docs define page_size as an integer with default 50:

The generated OpenAPI types page_size as a string and does not document the default:

  • /v2/clients/{client_id}/apps/{app_id}/history in services/amm/agent-web-api/openapi.json#L5463

Documented request example

GET /v2/clients/{client_id}/apps/{app_id}/history?page_size=50&cursor=...

Current generated parameter shape

page_size: string
cursor: string

Notes

Route coverage is mostly aligned

I did not find a missing documented v2 apps route in the generated contract. All routes documented under docs/api/apps are present in services/amm/agent-web-api/openapi.json.

GET /apps/{app_id} and PATCH /apps/{app_id} are closer than list

The outer AppDetailDto shape is broadly aligned with the docs:

  • id
  • catalog_app
  • is_favorite
  • is_approved
  • license_count
  • created_at
  • updated_at

The main drift on detail/update responses is the embedded CatalogAppEmbedDto typing described above.

Approval bulk response shape is closer than favorites

BulkApprovalsResponseDto is close to the documented succeeded[] and failed[] contract. The main remaining mismatch there is the missing documented 404 response on the bulk approval routes.

Requested Action

Please update the NestJS DTOs, decorators, and controller response metadata so that the generated OpenAPI matches the documented v2 apps API in docs/api/apps, then regenerate services/amm/agent-web-api/openapi.json and validate the contract.