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}/appsinservices/amm/agent-web-api/openapi.json#L4764/v2/clients/{client_id}/apps/{app_id}inservices/amm/agent-web-api/openapi.json#L4919/v2/clients/{client_id}/apps/favoritesinservices/amm/agent-web-api/openapi.json#L5103/v2/clients/{client_id}/apps/favorites/removeinservices/amm/agent-web-api/openapi.json#L5188/v2/clients/{client_id}/apps/approvalsinservices/amm/agent-web-api/openapi.json#L5273/v2/clients/{client_id}/apps/approvals/removeinservices/amm/agent-web-api/openapi.json#L5358/v2/clients/{client_id}/apps/{app_id}/historyinservices/amm/agent-web-api/openapi.json#L5443/v2/clients/{client_id}/apps/{app_id}/history/notesinservices/amm/agent-web-api/openapi.json#L5552AppListItemDtoinservices/amm/agent-web-api/openapi.json#L13209CatalogVendorEmbedDtoinservices/amm/agent-web-api/openapi.json#L13306CatalogAppEmbedDtoinservices/amm/agent-web-api/openapi.json#L13343AppDetailDtoinservices/amm/agent-web-api/openapi.json#L13404BulkUpdateResponseDtoinservices/amm/agent-web-api/openapi.json#L13476BulkApprovalsResponseDtoinservices/amm/agent-web-api/openapi.json#L13507AppHistoryEventDtoinservices/amm/agent-web-api/openapi.json#L13547AppHistoryListResponseDtoinservices/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:
AppsListResponseDtoinservices/amm/agent-web-api/openapi.json#L13245AppListItemDtoinservices/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}/appsinservices/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_idsis documented, but missing from OpenAPIcategory_idsis documented, but OpenAPI exposescategory_idinsteadtypeis documented, but missing from OpenAPIsort_byvalues do not match:- docs:
canonical_name,created_at,updated_at - OpenAPI:
app_name,date_added,created_at
- docs:
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:
CatalogAppEmbedDtoinservices/amm/agent-web-api/openapi.json#L13343CatalogVendorEmbedDtoinservices/amm/agent-web-api/openapi.json#L13306AppDetailDtoinservices/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:
descriptionis documented asstring | null, but OpenAPI types it as nullableobjectdisplay_colouris documented asstring | null, but OpenAPI types it as nullableobjectlogois documented asstring | null, but OpenAPI types it as nullableobjectvendor.urlis documented asstring | null, but OpenAPI types it as nullableobjecttypeis 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/favoritesinservices/amm/agent-web-api/openapi.json#L5103/v2/clients/{client_id}/apps/favorites/removeinservices/amm/agent-web-api/openapi.json#L5188BulkUpdateResponseDtoinservices/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}/appsinservices/amm/agent-web-api/openapi.json#L4857/v2/clients/{client_id}/apps/favoritesinservices/amm/agent-web-api/openapi.json#L5126/v2/clients/{client_id}/apps/favorites/removeinservices/amm/agent-web-api/openapi.json#L5211/v2/clients/{client_id}/apps/approvalsinservices/amm/agent-web-api/openapi.json#L5296/v2/clients/{client_id}/apps/approvals/removeinservices/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:
AppHistoryEventDtoinservices/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:
messageis documented asstring | null, but OpenAPI types it as nullableobjectcommentis documented asstring | null, but OpenAPI types it as nullableobjectmetadatais documented with event-specific shapes, but OpenAPI exposes only unconstrainedobject
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}/historyinservices/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:
idcatalog_appis_favoriteis_approvedlicense_countcreated_atupdated_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.