@thinkai/tai-api-contract 2.48.0 → 2.50.0-pr.877.6ff333f6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  openapi: 3.0.3
2
2
  info:
3
3
  title: ThinkAI API
4
- version: 2.48.0
4
+ version: 2.50.0
5
5
  description: >
6
6
  Contract surface for the AI Driven SDLC backend used by ThinkAI.
7
7
  Workspace-scoped routes use `/workspaces/{workspaceId}/...`.
@@ -60,6 +60,10 @@ tags:
60
60
  description: Platform-admin operations restricted to PLATFORM_ADMIN_EMAILS. Callers must present a valid JWT from an email on that allowlist; all other callers receive 403.
61
61
  - name: HRIS
62
62
  description: HRIS integrations (Personio, ZenHR, …); absence data, sync status, and manual sync (issue #447).
63
+ - name: PrivacyRequests
64
+ description: >
65
+ Data subject access / erasure / portability request intake and privacy-ops management (DSAR, issue #611).
66
+ Public and authenticated submission; platform-admin queue, export, and fulfillment.
63
67
 
64
68
  paths:
65
69
  /admin/users/{userId}:
@@ -296,6 +300,400 @@ paths:
296
300
  error: Forbidden
297
301
  code: forbidden
298
302
 
303
+ /privacy-requests:
304
+ post:
305
+ tags: [PrivacyRequests]
306
+ summary: Submit a privacy rights request (public)
307
+ operationId: submitPublicPrivacyRequest
308
+ security: []
309
+ description: >
310
+ Public, unauthenticated intake for data subject access, erasure, portability, rectification,
311
+ restriction, and objection requests (#611). No bearer token required.
312
+
313
+ **Abuse controls:** requests are rate limited per client IP (`429` with `Retry-After` when
314
+ exceeded). A hidden honeypot field (`companyWebsite`) silently absorbs bot submissions. The
315
+ response shape is identical whether or not the email matches an existing account, so the
316
+ endpoint cannot be used to enumerate users. Duplicate open requests for the same email and
317
+ request type within 24h are rejected with `409`.
318
+
319
+ **Identity is not proven** by this route; privacy ops must verify identity before any export
320
+ or erasure is fulfilled.
321
+ requestBody:
322
+ required: true
323
+ content:
324
+ application/json:
325
+ schema:
326
+ $ref: "#/components/schemas/CreatePrivacyRequestBodyDto"
327
+ responses:
328
+ "201":
329
+ description: Request accepted and queued for privacy ops.
330
+ content:
331
+ application/json:
332
+ schema:
333
+ $ref: "#/components/schemas/CreatePrivacyRequestResultDto"
334
+ "400":
335
+ description: Missing or invalid email, unknown request type, or details too long.
336
+ content:
337
+ application/json:
338
+ schema:
339
+ $ref: "#/components/schemas/ErrorMessageDto"
340
+ example:
341
+ error: A valid email is required
342
+ code: invalid_email
343
+ "409":
344
+ description: An open request for this email and type already exists.
345
+ content:
346
+ application/json:
347
+ schema:
348
+ $ref: "#/components/schemas/ErrorMessageDto"
349
+ example:
350
+ error: An open request for this email already exists
351
+ code: duplicate_open_request
352
+ "429":
353
+ description: Too many submissions from this client; retry later.
354
+ content:
355
+ application/json:
356
+ schema:
357
+ $ref: "#/components/schemas/ErrorMessageDto"
358
+ example:
359
+ error: Too many requests
360
+ code: too_many_requests
361
+
362
+ /me/privacy-requests:
363
+ get:
364
+ tags: [PrivacyRequests, Me]
365
+ summary: List the caller's privacy requests
366
+ operationId: listMyPrivacyRequests
367
+ description: >
368
+ Returns privacy requests whose subject is the authenticated caller (matched by user id or
369
+ email), limited to a reference code and status so callers can track progress.
370
+ responses:
371
+ "200":
372
+ description: Caller's privacy requests, newest first.
373
+ content:
374
+ application/json:
375
+ schema:
376
+ $ref: "#/components/schemas/MyPrivacyRequestListDto"
377
+ "401":
378
+ $ref: "#/components/responses/Unauthorized"
379
+ post:
380
+ tags: [PrivacyRequests, Me]
381
+ summary: Submit a privacy request as the authenticated user
382
+ operationId: submitMyPrivacyRequest
383
+ description: >
384
+ Authenticated intake. The subject is always the caller's own profile; the request is
385
+ auto-marked identity-verified because the JWT proves identity. Supplying an `email` that
386
+ does not match the caller is rejected with `400`.
387
+ requestBody:
388
+ required: true
389
+ content:
390
+ application/json:
391
+ schema:
392
+ $ref: "#/components/schemas/CreateMyPrivacyRequestBodyDto"
393
+ responses:
394
+ "201":
395
+ description: Request accepted and queued.
396
+ content:
397
+ application/json:
398
+ schema:
399
+ $ref: "#/components/schemas/CreatePrivacyRequestResultDto"
400
+ "400":
401
+ description: Unknown request type, details too long, or email override attempted.
402
+ content:
403
+ application/json:
404
+ schema:
405
+ $ref: "#/components/schemas/ErrorMessageDto"
406
+ example:
407
+ error: Email cannot be overridden on an authenticated request
408
+ code: email_override_rejected
409
+ "401":
410
+ $ref: "#/components/responses/Unauthorized"
411
+ "409":
412
+ description: An open request for this subject and type already exists.
413
+ content:
414
+ application/json:
415
+ schema:
416
+ $ref: "#/components/schemas/ErrorMessageDto"
417
+
418
+ /workspaces/{workspaceId}/privacy-requests:
419
+ post:
420
+ tags: [PrivacyRequests, Workspace]
421
+ summary: Submit a privacy request on behalf of a workspace member (B2B controller)
422
+ operationId: submitWorkspacePrivacyRequest
423
+ description: >
424
+ Workspace-admin route for B2B controllers to raise a privacy request on behalf of an
425
+ employee (`subjectEmail`). The request is bound to the workspace and requires manual
426
+ identity verification by privacy ops before fulfillment. Non-admin members receive `403`.
427
+ parameters:
428
+ - $ref: "#/components/parameters/WorkspaceId"
429
+ requestBody:
430
+ required: true
431
+ content:
432
+ application/json:
433
+ schema:
434
+ $ref: "#/components/schemas/CreateWorkspacePrivacyRequestBodyDto"
435
+ responses:
436
+ "201":
437
+ description: Request accepted and queued.
438
+ content:
439
+ application/json:
440
+ schema:
441
+ $ref: "#/components/schemas/CreatePrivacyRequestResultDto"
442
+ "400":
443
+ description: Invalid subject email, unknown request type, or details too long.
444
+ content:
445
+ application/json:
446
+ schema:
447
+ $ref: "#/components/schemas/ErrorMessageDto"
448
+ "401":
449
+ $ref: "#/components/responses/Unauthorized"
450
+ "403":
451
+ description: Caller is not a workspace admin.
452
+ content:
453
+ application/json:
454
+ schema:
455
+ $ref: "#/components/schemas/ErrorMessageDto"
456
+ example:
457
+ error: Forbidden
458
+ code: workspace_admin_required
459
+ "404":
460
+ description: Workspace not found.
461
+ content:
462
+ application/json:
463
+ schema:
464
+ $ref: "#/components/schemas/ErrorMessageDto"
465
+
466
+ /admin/privacy-requests:
467
+ get:
468
+ tags: [PrivacyRequests, PlatformAdmin]
469
+ summary: List privacy requests (privacy-ops queue)
470
+ operationId: adminListPrivacyRequests
471
+ description: >
472
+ Platform-admin queue of privacy requests, newest first, with SLA due dates for triage.
473
+
474
+ **Authorization:** Bearer JWT required. Caller email must appear in `PLATFORM_ADMIN_EMAILS`.
475
+ parameters:
476
+ - name: status
477
+ in: query
478
+ required: false
479
+ schema:
480
+ $ref: "#/components/schemas/PrivacyRequestStatus"
481
+ description: Filter by workflow status.
482
+ - name: requestType
483
+ in: query
484
+ required: false
485
+ schema:
486
+ $ref: "#/components/schemas/PrivacyRequestType"
487
+ description: Filter by request type.
488
+ - name: overdue
489
+ in: query
490
+ required: false
491
+ schema:
492
+ type: boolean
493
+ description: When true, return only requests past their SLA due date and not yet completed.
494
+ - name: limit
495
+ in: query
496
+ required: false
497
+ schema:
498
+ type: integer
499
+ minimum: 1
500
+ maximum: 200
501
+ default: 100
502
+ description: Maximum number of rows to return (default 100, max 200).
503
+ responses:
504
+ "200":
505
+ description: Privacy request queue.
506
+ content:
507
+ application/json:
508
+ schema:
509
+ $ref: "#/components/schemas/PrivacyRequestListDto"
510
+ "401":
511
+ $ref: "#/components/responses/Unauthorized"
512
+ "403":
513
+ description: Caller is not a platform admin.
514
+ content:
515
+ application/json:
516
+ schema:
517
+ $ref: "#/components/schemas/ErrorMessageDto"
518
+ example:
519
+ error: Forbidden
520
+ code: forbidden
521
+
522
+ /admin/privacy-requests/{requestId}:
523
+ get:
524
+ tags: [PrivacyRequests, PlatformAdmin]
525
+ summary: Get a privacy request with its event timeline
526
+ operationId: adminGetPrivacyRequest
527
+ parameters:
528
+ - $ref: "#/components/parameters/PrivacyRequestId"
529
+ responses:
530
+ "200":
531
+ description: Privacy request detail with audit timeline.
532
+ content:
533
+ application/json:
534
+ schema:
535
+ $ref: "#/components/schemas/PrivacyRequestDetailDto"
536
+ "401":
537
+ $ref: "#/components/responses/Unauthorized"
538
+ "403":
539
+ description: Caller is not a platform admin.
540
+ content:
541
+ application/json:
542
+ schema:
543
+ $ref: "#/components/schemas/ErrorMessageDto"
544
+ "404":
545
+ description: Request not found.
546
+ content:
547
+ application/json:
548
+ schema:
549
+ $ref: "#/components/schemas/ErrorMessageDto"
550
+ patch:
551
+ tags: [PrivacyRequests, PlatformAdmin]
552
+ summary: Update a privacy request (status, assignee, notes, identity verification)
553
+ operationId: adminUpdatePrivacyRequest
554
+ description: >
555
+ Update workflow fields. Setting `markIdentityVerified: true` records the verifying admin and
556
+ unlocks export and erasure fulfillment. Transitioning `status` to `completed` requires both
557
+ `fulfillmentAction` and `fulfillmentRef`.
558
+ parameters:
559
+ - $ref: "#/components/parameters/PrivacyRequestId"
560
+ requestBody:
561
+ required: true
562
+ content:
563
+ application/json:
564
+ schema:
565
+ $ref: "#/components/schemas/UpdatePrivacyRequestBodyDto"
566
+ responses:
567
+ "200":
568
+ description: Updated privacy request detail.
569
+ content:
570
+ application/json:
571
+ schema:
572
+ $ref: "#/components/schemas/PrivacyRequestDetailDto"
573
+ "400":
574
+ description: Invalid field value or missing fulfillment metadata for completion.
575
+ content:
576
+ application/json:
577
+ schema:
578
+ $ref: "#/components/schemas/ErrorMessageDto"
579
+ example:
580
+ error: Completing a request requires a fulfillment action and reference
581
+ code: fulfillment_metadata_required
582
+ "401":
583
+ $ref: "#/components/responses/Unauthorized"
584
+ "403":
585
+ description: Caller is not a platform admin.
586
+ content:
587
+ application/json:
588
+ schema:
589
+ $ref: "#/components/schemas/ErrorMessageDto"
590
+ "404":
591
+ description: Request not found.
592
+ content:
593
+ application/json:
594
+ schema:
595
+ $ref: "#/components/schemas/ErrorMessageDto"
596
+
597
+ /admin/privacy-requests/{requestId}/export:
598
+ post:
599
+ tags: [PrivacyRequests, PlatformAdmin]
600
+ summary: Generate a DSAR export bundle for a privacy request
601
+ operationId: adminExportPrivacyRequest
602
+ description: >
603
+ Assembles the subject's personal data across workspaces into a single JSON bundle for an
604
+ access or portability request, per the PII inventory scope. The subject is resolved by user
605
+ id or email. Blocked until identity is verified (`403 identity_not_verified`). Bundles larger
606
+ than the size cap return `413 export_too_large`; scope by workspace in that case. The bundle
607
+ is generated on demand and not persisted server-side.
608
+
609
+ **Authorization:** Bearer JWT required. Caller email must appear in `PLATFORM_ADMIN_EMAILS`.
610
+ parameters:
611
+ - $ref: "#/components/parameters/PrivacyRequestId"
612
+ responses:
613
+ "200":
614
+ description: DSAR export bundle (also offered as a file download via Content-Disposition).
615
+ content:
616
+ application/json:
617
+ schema:
618
+ $ref: "#/components/schemas/PrivacyRequestExportDto"
619
+ "401":
620
+ $ref: "#/components/responses/Unauthorized"
621
+ "403":
622
+ description: Caller is not a platform admin, or identity has not been verified.
623
+ content:
624
+ application/json:
625
+ schema:
626
+ $ref: "#/components/schemas/ErrorMessageDto"
627
+ example:
628
+ error: Identity must be verified before export
629
+ code: identity_not_verified
630
+ "404":
631
+ description: Request not found.
632
+ content:
633
+ application/json:
634
+ schema:
635
+ $ref: "#/components/schemas/ErrorMessageDto"
636
+ "413":
637
+ description: Export exceeds the size cap; scope the request by workspace.
638
+ content:
639
+ application/json:
640
+ schema:
641
+ $ref: "#/components/schemas/ErrorMessageDto"
642
+ example:
643
+ error: Export bundle is too large; scope by workspace
644
+ code: export_too_large
645
+
646
+ /admin/privacy-requests/{requestId}/fulfill/person-erasure:
647
+ post:
648
+ tags: [PrivacyRequests, PlatformAdmin]
649
+ summary: Fulfill an erasure request by erasing workspace person-level data
650
+ operationId: adminFulfillPrivacyRequestPersonErasure
651
+ description: >
652
+ Thin wrapper around workspace person-level insights erasure (#706) for privacy-request
653
+ fulfillment. Erases or anonymizes the subject's org-chart, AI-tool, dashboard, absence, and
654
+ readiness data in one workspace. Blocked until identity is verified. Records an audit event
655
+ on the privacy request with the categories erased.
656
+
657
+ **Authorization:** Bearer JWT required. Caller email must appear in `PLATFORM_ADMIN_EMAILS`.
658
+ parameters:
659
+ - $ref: "#/components/parameters/PrivacyRequestId"
660
+ requestBody:
661
+ required: true
662
+ content:
663
+ application/json:
664
+ schema:
665
+ $ref: "#/components/schemas/FulfillPersonErasureBodyDto"
666
+ responses:
667
+ "200":
668
+ description: Erasure result.
669
+ content:
670
+ application/json:
671
+ schema:
672
+ $ref: "#/components/schemas/PrivacyRequestPersonErasureResultDto"
673
+ "400":
674
+ description: Missing workspaceId/personId or invalid options.
675
+ content:
676
+ application/json:
677
+ schema:
678
+ $ref: "#/components/schemas/ErrorMessageDto"
679
+ "401":
680
+ $ref: "#/components/responses/Unauthorized"
681
+ "403":
682
+ description: Caller is not a platform admin, or identity has not been verified.
683
+ content:
684
+ application/json:
685
+ schema:
686
+ $ref: "#/components/schemas/ErrorMessageDto"
687
+ example:
688
+ error: Identity must be verified before erasure
689
+ code: identity_not_verified
690
+ "404":
691
+ description: Request not found, or person not found in the workspace org chart.
692
+ content:
693
+ application/json:
694
+ schema:
695
+ $ref: "#/components/schemas/ErrorMessageDto"
696
+
299
697
  /me:
300
698
  get:
301
699
  tags: [Me]
@@ -1172,6 +1570,8 @@ paths:
1172
1570
  parameters:
1173
1571
  - $ref: "#/components/parameters/WorkspaceId"
1174
1572
  - $ref: "#/components/parameters/InsightsProductivityRangeId"
1573
+ - $ref: "#/components/parameters/InsightsProductivityFrom"
1574
+ - $ref: "#/components/parameters/InsightsProductivityTo"
1175
1575
  - $ref: "#/components/parameters/InsightsDepartmentFilter"
1176
1576
  - name: team
1177
1577
  in: query
@@ -1220,6 +1620,8 @@ paths:
1220
1620
  parameters:
1221
1621
  - $ref: "#/components/parameters/WorkspaceId"
1222
1622
  - $ref: "#/components/parameters/InsightsProductivityRangeId"
1623
+ - $ref: "#/components/parameters/InsightsProductivityFrom"
1624
+ - $ref: "#/components/parameters/InsightsProductivityTo"
1223
1625
  - $ref: "#/components/parameters/InsightsDepartmentFilter"
1224
1626
  - name: team
1225
1627
  in: query
@@ -1284,6 +1686,8 @@ paths:
1284
1686
  maximum: 10
1285
1687
  default: 3
1286
1688
  - $ref: "#/components/parameters/InsightsProductivityRangeId"
1689
+ - $ref: "#/components/parameters/InsightsProductivityFrom"
1690
+ - $ref: "#/components/parameters/InsightsProductivityTo"
1287
1691
  - $ref: "#/components/parameters/InsightsDepartmentFilter"
1288
1692
  - name: team
1289
1693
  in: query
@@ -1349,6 +1753,8 @@ paths:
1349
1753
  schema:
1350
1754
  type: string
1351
1755
  - $ref: "#/components/parameters/InsightsProductivityRangeId"
1756
+ - $ref: "#/components/parameters/InsightsProductivityFrom"
1757
+ - $ref: "#/components/parameters/InsightsProductivityTo"
1352
1758
  - $ref: "#/components/parameters/InsightsDepartmentFilter"
1353
1759
  - name: team
1354
1760
  in: query
@@ -1422,6 +1828,8 @@ paths:
1422
1828
  maximum: 10
1423
1829
  default: 3
1424
1830
  - $ref: "#/components/parameters/InsightsProductivityRangeId"
1831
+ - $ref: "#/components/parameters/InsightsProductivityFrom"
1832
+ - $ref: "#/components/parameters/InsightsProductivityTo"
1425
1833
  - $ref: "#/components/parameters/InsightsDepartmentFilter"
1426
1834
  - name: team
1427
1835
  in: query
@@ -1737,11 +2145,11 @@ paths:
1737
2145
  summary: AI usage & spend insights — AI-assisted code percentage
1738
2146
  description: >
1739
2147
  Period and weekly AI-assisted code percentage for the Measure **AI-assisted code**
1740
- card on `/phase-c/c7` and the **AI impact** curve on `/phase-c/c4`. When scoped
1741
- productivity metrics include `aiAssistedContributionRatioPercent`, values are
1742
- **measured** from ingest; otherwise the API falls back to token→LOC heuristics and
1743
- sets `dataQuality` to **estimated**. Clients should not present estimated series as
1744
- Cursor-measured signal.
2148
+ card on `/phase-c/c7` and the **AI impact** curve on `/phase-c/c4`. When engagement
2149
+ daily rows exist for the scope, values are **measured** from Σ `ai_lines_added` ÷
2150
+ merged-PR lines (`dashboard_metrics`); otherwise the API falls back to token→LOC
2151
+ heuristics and sets `dataQuality` to **estimated**. Clients should not present
2152
+ estimated series as editor-measured signal.
1745
2153
  operationId: getAiUsageSpendAiAssistedCodePercentage
1746
2154
  parameters:
1747
2155
  - $ref: "#/components/parameters/WorkspaceId"
@@ -1788,6 +2196,62 @@ paths:
1788
2196
  "404":
1789
2197
  description: Workspace does not exist or malformed workspaceId
1790
2198
 
2199
+ /workspaces/{workspaceId}/insights/ai-usage-spend/provider-engagement-insights:
2200
+ get:
2201
+ tags: [Workspace]
2202
+ summary: AI usage & spend insights — provider engagement cards
2203
+ description: >
2204
+ Provider-specific engagement insight metrics derived from
2205
+ `tai_workspace_ai_tool_engagement_daily` (accepted AI lines, tab completions,
2206
+ suggestion accept rates, Claude Code sessions, etc.). Metrics are
2207
+ **provider- and data-gated**: structurally absent signals render as N/A, not zero.
2208
+ Claude per-user cards require Enterprise Analytics (`insightsPlan` ≠ team).
2209
+ operationId: getAiUsageSpendProviderEngagementInsights
2210
+ parameters:
2211
+ - $ref: "#/components/parameters/WorkspaceId"
2212
+ - name: rangeId
2213
+ in: query
2214
+ description: Measure time range (maps to weekly bucket count).
2215
+ schema:
2216
+ type: string
2217
+ enum: [4w, 8w, q]
2218
+ default: 4w
2219
+ - $ref: "#/components/parameters/InsightsDepartmentFilter"
2220
+ - name: team
2221
+ in: query
2222
+ description: Team name filter; send `All teams` to include every team.
2223
+ schema:
2224
+ type: string
2225
+ default: All teams
2226
+ - name: windowEnd
2227
+ in: query
2228
+ description: ISO timestamp for the end of the last weekly bucket; omit for current week.
2229
+ schema:
2230
+ type: string
2231
+ format: date-time
2232
+ responses:
2233
+ "200":
2234
+ description: Provider engagement insight cards
2235
+ content:
2236
+ application/json:
2237
+ schema:
2238
+ $ref: "#/components/schemas/AiUsageSpendProviderEngagementInsightsDto"
2239
+ "400":
2240
+ description: Invalid query parameters
2241
+ content:
2242
+ application/json:
2243
+ schema:
2244
+ type: object
2245
+ required: [error]
2246
+ properties:
2247
+ error: { type: string, example: invalid_query }
2248
+ "401":
2249
+ $ref: "#/components/responses/Unauthorized"
2250
+ "403":
2251
+ $ref: "#/components/responses/Forbidden"
2252
+ "404":
2253
+ description: Workspace does not exist or malformed workspaceId
2254
+
1791
2255
  /workspaces/{workspaceId}/insights/ai-usage-spend/ai-tool-usage-spend:
1792
2256
  get:
1793
2257
  tags: [Workspace]
@@ -5705,6 +6169,14 @@ components:
5705
6169
  type: string
5706
6170
  format: uuid
5707
6171
  description: Workspace member user id (`tai_users.id`).
6172
+ PrivacyRequestId:
6173
+ name: requestId
6174
+ in: path
6175
+ required: true
6176
+ schema:
6177
+ type: string
6178
+ format: uuid
6179
+ description: Privacy request id (`tai_privacy_requests.id`).
5708
6180
  FixId:
5709
6181
  name: fixId
5710
6182
  in: path
@@ -5716,12 +6188,30 @@ components:
5716
6188
  name: rangeId
5717
6189
  in: query
5718
6190
  description: >
5719
- Productivity insights time range in weeks (`4w`=4, `8w`=8, `12w`=12, `16w`=16).
5720
- Legacy `q` is an alias for 12 weeks.
6191
+ Productivity insights time range. Daily windows: `7d`, `30d`, `90d`, `180d`, or custom
6192
+ `cr:YYYY-MM-DD:YYYY-MM-DD`. Weekly legacy: `4w`, `8w`, `q` (12 weeks), `12w`, `16w`.
6193
+ Omit when using explicit `from`/`to` day bounds.
5721
6194
  schema:
5722
6195
  type: string
5723
- enum: [4w, 8w, q, 12w, 16w]
5724
6196
  default: 4w
6197
+ InsightsProductivityFrom:
6198
+ name: from
6199
+ in: query
6200
+ description: >
6201
+ Inclusive start of a daily window (`YYYY-MM-DD`). When set with `to`, overrides `rangeId`
6202
+ for day-mode responses (max 180 days).
6203
+ schema:
6204
+ type: string
6205
+ format: date
6206
+ InsightsProductivityTo:
6207
+ name: to
6208
+ in: query
6209
+ description: >
6210
+ Inclusive end of a daily window (`YYYY-MM-DD`). When set with `from`, overrides `rangeId`
6211
+ for day-mode responses (max 180 days).
6212
+ schema:
6213
+ type: string
6214
+ format: date
5725
6215
  InsightsDepartmentFilter:
5726
6216
  name: department
5727
6217
  in: query
@@ -6687,6 +7177,32 @@ components:
6687
7177
  type: string
6688
7178
  description: Applied team name filter from the Measure UI.
6689
7179
 
7180
+ ProductivityInsightsGranularity:
7181
+ type: string
7182
+ enum: [day, week]
7183
+ description: >
7184
+ Echo of the resolved time bucket for this response. `day` when the request used an
7185
+ `Nd`/`cr:`/`from`+`to` window; `week` for legacy weekly rangeIds or `year` heatmap mode.
7186
+
7187
+ ProductivityInsightsDailyBucketDto:
7188
+ type: object
7189
+ additionalProperties: false
7190
+ required: [date, mergedPrCount]
7191
+ properties:
7192
+ date:
7193
+ type: string
7194
+ format: date
7195
+ description: UTC calendar day (`YYYY-MM-DD`).
7196
+ mergedPrCount:
7197
+ type: number
7198
+ minimum: 0
7199
+ ciFailuresCount:
7200
+ type: integer
7201
+ minimum: 0
7202
+ description: >
7203
+ GitHub Actions CI build failures on this day from `ciFailureRuns` ledger when CI
7204
+ ingest is enabled.
7205
+
6690
7206
  ProductivityInsightsAggregatedTeamWeekDto:
6691
7207
  type: object
6692
7208
  additionalProperties: false
@@ -6734,7 +7250,7 @@ components:
6734
7250
  ProductivityInsightsAggregatedTeamRowDto:
6735
7251
  type: object
6736
7252
  additionalProperties: false
6737
- required: [teamId, teamName, headcount, weeks]
7253
+ required: [teamId, teamName, headcount]
6738
7254
  properties:
6739
7255
  teamId:
6740
7256
  type: string
@@ -6748,6 +7264,11 @@ components:
6748
7264
  notInOrgChart:
6749
7265
  type: boolean
6750
7266
  description: True when the team aggregates GitHub logins not mapped in the org chart (`teamId` `__other`).
7267
+ days:
7268
+ type: array
7269
+ description: Present in day-mode responses — daily merged PR and CI failure counts.
7270
+ items:
7271
+ $ref: "#/components/schemas/ProductivityInsightsDailyBucketDto"
6751
7272
  weeks:
6752
7273
  type: array
6753
7274
  items:
@@ -6755,9 +7276,9 @@ components:
6755
7276
 
6756
7277
  ProductivityInsightsRangeId:
6757
7278
  type: string
6758
- enum: [4w, 8w, q, 12w, 16w]
6759
7279
  description: >
6760
- Weekly bucket count (`4w`=4, `8w`=8, `12w`=12, `16w`=16). Legacy `q` aliases 12 weeks.
7280
+ Daily windows: `7d`, `30d`, `90d`, `180d`, or custom `cr:YYYY-MM-DD:YYYY-MM-DD`.
7281
+ Weekly legacy: `4w`, `8w`, `q` (12 weeks), `12w`, `16w`.
6761
7282
 
6762
7283
  ProductivityInsightsAggregatedTeamsDto:
6763
7284
  type: object
@@ -6769,6 +7290,16 @@ components:
6769
7290
  format: uuid
6770
7291
  range:
6771
7292
  $ref: "#/components/schemas/ProductivityInsightsRangeId"
7293
+ granularity:
7294
+ $ref: "#/components/schemas/ProductivityInsightsGranularity"
7295
+ from:
7296
+ type: string
7297
+ format: date
7298
+ description: Inclusive start day when `granularity` is `day`.
7299
+ to:
7300
+ type: string
7301
+ format: date
7302
+ description: Inclusive end day when `granularity` is `day`.
6772
7303
  scope:
6773
7304
  $ref: "#/components/schemas/ProductivityInsightsAggregatedTeamsScopeDto"
6774
7305
  generatedAt:
@@ -6802,7 +7333,7 @@ components:
6802
7333
  ProductivityInsightsMergedPrContributorRowDto:
6803
7334
  type: object
6804
7335
  additionalProperties: false
6805
- required: [personId, personName, weeks]
7336
+ required: [personId, personName]
6806
7337
  properties:
6807
7338
  personId:
6808
7339
  type: string
@@ -6820,6 +7351,11 @@ components:
6820
7351
  githubLogin:
6821
7352
  type: string
6822
7353
  description: GitHub login for unattributed contributors (when notInOrgChart is true).
7354
+ days:
7355
+ type: array
7356
+ description: Present in day-mode responses — daily merged PR counts per contributor.
7357
+ items:
7358
+ $ref: "#/components/schemas/ProductivityInsightsDailyBucketDto"
6823
7359
  weeks:
6824
7360
  type: array
6825
7361
  items:
@@ -6828,23 +7364,40 @@ components:
6828
7364
  ProductivityInsightsMergedPrTrendsDto:
6829
7365
  type: object
6830
7366
  additionalProperties: false
6831
- required: [workspaceId, range, scope, generatedAt, weeks, contributors]
7367
+ required: [workspaceId, range, scope, generatedAt, contributors]
6832
7368
  properties:
6833
7369
  workspaceId:
6834
7370
  type: string
6835
7371
  format: uuid
6836
7372
  range:
6837
7373
  $ref: "#/components/schemas/ProductivityInsightsRangeId"
7374
+ granularity:
7375
+ $ref: "#/components/schemas/ProductivityInsightsGranularity"
7376
+ from:
7377
+ type: string
7378
+ format: date
7379
+ description: Inclusive start day when `granularity` is `day`.
7380
+ to:
7381
+ type: string
7382
+ format: date
7383
+ description: Inclusive end day when `granularity` is `day`.
6838
7384
  scope:
6839
7385
  $ref: "#/components/schemas/ProductivityInsightsAggregatedTeamsScopeDto"
6840
7386
  generatedAt:
6841
7387
  type: string
6842
7388
  format: date-time
7389
+ days:
7390
+ type: array
7391
+ description: >-
7392
+ Portfolio daily merged PR series in day mode (max of contributor sum and org ledger).
7393
+ items:
7394
+ $ref: "#/components/schemas/ProductivityInsightsDailyBucketDto"
6843
7395
  weeks:
6844
7396
  type: array
6845
7397
  description: >-
6846
7398
  Sum of contributor weekly merged PR counts (matches the contributors table).
6847
7399
  Not deduplicated org-wide unique PRs when the same PR appears on multiple rows.
7400
+ Present in week-mode responses.
6848
7401
  items:
6849
7402
  $ref: "#/components/schemas/ProductivityInsightsMergedPrTrendWeekDto"
6850
7403
  contributors:
@@ -6901,7 +7454,7 @@ components:
6901
7454
  ProductivityInsightsTeamDetailDto:
6902
7455
  type: object
6903
7456
  additionalProperties: false
6904
- required: [teamId, teamName, headcount, weeks, summary]
7457
+ required: [teamId, teamName, headcount, summary]
6905
7458
  properties:
6906
7459
  teamId:
6907
7460
  type: string
@@ -6920,6 +7473,13 @@ components:
6920
7473
  minimum: 2000
6921
7474
  maximum: 2100
6922
7475
  description: Present when the request included `year` — calendar-year heatmap mode.
7476
+ days:
7477
+ type: array
7478
+ description: >-
7479
+ Daily merged PR and CI failure counts for the selected period when `granularity` is
7480
+ `day` (not present in `year` heatmap mode).
7481
+ items:
7482
+ $ref: "#/components/schemas/ProductivityInsightsDailyBucketDto"
6923
7483
  heatmapWeeks:
6924
7484
  type: array
6925
7485
  description: >-
@@ -6953,6 +7513,16 @@ components:
6953
7513
  format: uuid
6954
7514
  range:
6955
7515
  $ref: "#/components/schemas/ProductivityInsightsRangeId"
7516
+ granularity:
7517
+ $ref: "#/components/schemas/ProductivityInsightsGranularity"
7518
+ from:
7519
+ type: string
7520
+ format: date
7521
+ description: Inclusive start day when `granularity` is `day`.
7522
+ to:
7523
+ type: string
7524
+ format: date
7525
+ description: Inclusive end day when `granularity` is `day`.
6956
7526
  year:
6957
7527
  type: integer
6958
7528
  minimum: 2000
@@ -7139,7 +7709,6 @@ components:
7139
7709
  - scope
7140
7710
  - generatedAt
7141
7711
  - contributor
7142
- - weeks
7143
7712
  - summary
7144
7713
  properties:
7145
7714
  workspaceId:
@@ -7149,6 +7718,16 @@ components:
7149
7718
  type: string
7150
7719
  range:
7151
7720
  $ref: "#/components/schemas/ProductivityInsightsRangeId"
7721
+ granularity:
7722
+ $ref: "#/components/schemas/ProductivityInsightsGranularity"
7723
+ from:
7724
+ type: string
7725
+ format: date
7726
+ description: Inclusive start day when `granularity` is `day`.
7727
+ to:
7728
+ type: string
7729
+ format: date
7730
+ description: Inclusive end day when `granularity` is `day`.
7152
7731
  year:
7153
7732
  type: integer
7154
7733
  minimum: 2000
@@ -7164,6 +7743,13 @@ components:
7164
7743
  description: True when the workspace has opted in to GitHub Actions CI ingest.
7165
7744
  contributor:
7166
7745
  $ref: "#/components/schemas/ProductivityInsightsContributorProfileDto"
7746
+ days:
7747
+ type: array
7748
+ description: >-
7749
+ Daily merged PR and CI failure counts for the selected period when `granularity` is
7750
+ `day` (not present in `year` heatmap mode).
7751
+ items:
7752
+ $ref: "#/components/schemas/ProductivityInsightsDailyBucketDto"
7167
7753
  weeks:
7168
7754
  type: array
7169
7755
  items:
@@ -7212,6 +7798,16 @@ components:
7212
7798
  format: uuid
7213
7799
  range:
7214
7800
  $ref: "#/components/schemas/ProductivityInsightsRangeId"
7801
+ granularity:
7802
+ $ref: "#/components/schemas/ProductivityInsightsGranularity"
7803
+ from:
7804
+ type: string
7805
+ format: date
7806
+ description: Inclusive start day when `granularity` is `day`.
7807
+ to:
7808
+ type: string
7809
+ format: date
7810
+ description: Inclusive end day when `granularity` is `day`.
7215
7811
  scope:
7216
7812
  $ref: "#/components/schemas/ProductivityInsightsAggregatedTeamsScopeDto"
7217
7813
  entityType:
@@ -7554,6 +8150,66 @@ components:
7554
8150
  items:
7555
8151
  $ref: "#/components/schemas/AiUsageSpendAiAssistedCodeWeekDto"
7556
8152
 
8153
+ ProviderEngagementInsightAvailability:
8154
+ type: string
8155
+ enum: [measured, not_applicable]
8156
+
8157
+ AiUsageSpendProviderEngagementMetricDto:
8158
+ type: object
8159
+ additionalProperties: false
8160
+ required: [id, label, value, displayValue, availability]
8161
+ properties:
8162
+ id:
8163
+ type: string
8164
+ label:
8165
+ type: string
8166
+ value:
8167
+ type: number
8168
+ nullable: true
8169
+ displayValue:
8170
+ type: string
8171
+ availability:
8172
+ $ref: "#/components/schemas/ProviderEngagementInsightAvailability"
8173
+ unit:
8174
+ type: string
8175
+ methodologyNote:
8176
+ type: string
8177
+
8178
+ AiUsageSpendProviderEngagementCardDto:
8179
+ type: object
8180
+ additionalProperties: false
8181
+ required: [providerId, providerName, metrics]
8182
+ properties:
8183
+ providerId:
8184
+ type: string
8185
+ providerName:
8186
+ type: string
8187
+ metrics:
8188
+ type: array
8189
+ items:
8190
+ $ref: "#/components/schemas/AiUsageSpendProviderEngagementMetricDto"
8191
+
8192
+ AiUsageSpendProviderEngagementInsightsDto:
8193
+ type: object
8194
+ additionalProperties: false
8195
+ required: [workspaceId, range, scope, generatedAt, providers]
8196
+ properties:
8197
+ workspaceId:
8198
+ type: string
8199
+ format: uuid
8200
+ range:
8201
+ type: string
8202
+ enum: [4w, 8w, q]
8203
+ scope:
8204
+ $ref: "#/components/schemas/ProductivityInsightsAggregatedTeamsScopeDto"
8205
+ generatedAt:
8206
+ type: string
8207
+ format: date-time
8208
+ providers:
8209
+ type: array
8210
+ items:
8211
+ $ref: "#/components/schemas/AiUsageSpendProviderEngagementCardDto"
8212
+
7557
8213
  AiUsageSpendAiToolUsageSpendProviderItemDto:
7558
8214
  type: object
7559
8215
  additionalProperties: false
@@ -7977,6 +8633,12 @@ components:
7977
8633
  defaultWorkspaceId:
7978
8634
  type: string
7979
8635
  nullable: true
8636
+ isPlatformAdmin:
8637
+ type: boolean
8638
+ description: >
8639
+ True when the caller's email is on the `PLATFORM_ADMIN_EMAILS` allowlist. Lets the
8640
+ frontend gate platform-admin surfaces (e.g. the privacy-request queue) without
8641
+ duplicating the allowlist client-side.
7980
8642
 
7981
8643
  UpdateMeBodyDto:
7982
8644
  type: object
@@ -8000,6 +8662,341 @@ components:
8000
8662
  Must match the authenticated user's email (case-insensitive). Acts as
8001
8663
  a type-to-confirm guard against accidental deletion.
8002
8664
 
8665
+ PrivacyRequestType:
8666
+ type: string
8667
+ enum: [access, erasure, portability, rectification, restriction, objection]
8668
+ description: Data subject right being exercised (GDPR Arts. 15–21 / UAE PDPL).
8669
+
8670
+ PrivacyRequestStatus:
8671
+ type: string
8672
+ enum: [pending, in_progress, completed, rejected]
8673
+ description: Workflow status of a privacy request.
8674
+
8675
+ PrivacyRequestIntakeChannel:
8676
+ type: string
8677
+ enum: [public, authenticated, workspace_admin]
8678
+ description: How the request was submitted.
8679
+
8680
+ CreatePrivacyRequestBodyDto:
8681
+ type: object
8682
+ required: [email, requestType]
8683
+ properties:
8684
+ email:
8685
+ type: string
8686
+ format: email
8687
+ description: Subject's email address.
8688
+ requestType:
8689
+ $ref: "#/components/schemas/PrivacyRequestType"
8690
+ details:
8691
+ type: string
8692
+ maxLength: 4000
8693
+ description: Optional free-text description of the request (max 4000 chars).
8694
+ workspaceHint:
8695
+ type: string
8696
+ maxLength: 200
8697
+ description: >
8698
+ Optional free-text hint (e.g. company or workspace name) to help privacy ops locate the
8699
+ subject. Never bound to a workspace id on the public channel.
8700
+ companyWebsite:
8701
+ type: string
8702
+ description: >
8703
+ Honeypot field. Must be left empty by real clients; non-empty submissions are silently
8704
+ accepted but not persisted. Rendered hidden in the UI.
8705
+
8706
+ CreateMyPrivacyRequestBodyDto:
8707
+ type: object
8708
+ required: [requestType]
8709
+ properties:
8710
+ requestType:
8711
+ $ref: "#/components/schemas/PrivacyRequestType"
8712
+ details:
8713
+ type: string
8714
+ maxLength: 4000
8715
+ email:
8716
+ type: string
8717
+ format: email
8718
+ description: >
8719
+ Optional. If provided it must match the caller's own email; a differing value is
8720
+ rejected with `400 email_override_rejected`.
8721
+
8722
+ CreateWorkspacePrivacyRequestBodyDto:
8723
+ type: object
8724
+ required: [subjectEmail, requestType]
8725
+ properties:
8726
+ subjectEmail:
8727
+ type: string
8728
+ format: email
8729
+ description: Email of the workspace member the request is raised on behalf of.
8730
+ requestType:
8731
+ $ref: "#/components/schemas/PrivacyRequestType"
8732
+ details:
8733
+ type: string
8734
+ maxLength: 4000
8735
+
8736
+ CreatePrivacyRequestResultDto:
8737
+ type: object
8738
+ required: [referenceCode, requestType, status, slaDueAt]
8739
+ properties:
8740
+ referenceCode:
8741
+ type: string
8742
+ description: Human-friendly tracking code (e.g. `PR-2026-00042`).
8743
+ requestType:
8744
+ $ref: "#/components/schemas/PrivacyRequestType"
8745
+ status:
8746
+ $ref: "#/components/schemas/PrivacyRequestStatus"
8747
+ slaDueAt:
8748
+ type: string
8749
+ format: date-time
8750
+ description: When the request should be resolved by (default 30 days from submission).
8751
+
8752
+ MyPrivacyRequestSummaryDto:
8753
+ type: object
8754
+ required: [referenceCode, requestType, status, createdAt, slaDueAt]
8755
+ properties:
8756
+ referenceCode:
8757
+ type: string
8758
+ requestType:
8759
+ $ref: "#/components/schemas/PrivacyRequestType"
8760
+ status:
8761
+ $ref: "#/components/schemas/PrivacyRequestStatus"
8762
+ createdAt:
8763
+ type: string
8764
+ format: date-time
8765
+ slaDueAt:
8766
+ type: string
8767
+ format: date-time
8768
+ completedAt:
8769
+ type: string
8770
+ format: date-time
8771
+ nullable: true
8772
+
8773
+ MyPrivacyRequestListDto:
8774
+ type: object
8775
+ required: [requests]
8776
+ properties:
8777
+ requests:
8778
+ type: array
8779
+ items:
8780
+ $ref: "#/components/schemas/MyPrivacyRequestSummaryDto"
8781
+
8782
+ PrivacyRequestDto:
8783
+ type: object
8784
+ required:
8785
+ [id, referenceCode, requestType, status, intakeChannel, subjectEmail, createdAt, updatedAt, slaDueAt]
8786
+ properties:
8787
+ id:
8788
+ type: string
8789
+ format: uuid
8790
+ referenceCode:
8791
+ type: string
8792
+ requestType:
8793
+ $ref: "#/components/schemas/PrivacyRequestType"
8794
+ status:
8795
+ $ref: "#/components/schemas/PrivacyRequestStatus"
8796
+ intakeChannel:
8797
+ $ref: "#/components/schemas/PrivacyRequestIntakeChannel"
8798
+ subjectEmail:
8799
+ type: string
8800
+ subjectUserId:
8801
+ type: string
8802
+ format: uuid
8803
+ nullable: true
8804
+ workspaceId:
8805
+ type: string
8806
+ format: uuid
8807
+ nullable: true
8808
+ description: Set only for workspace-admin (B2B) submissions.
8809
+ subjectWorkspaceHint:
8810
+ type: string
8811
+ nullable: true
8812
+ requestedByUserId:
8813
+ type: string
8814
+ format: uuid
8815
+ nullable: true
8816
+ details:
8817
+ type: string
8818
+ nullable: true
8819
+ internalNotes:
8820
+ type: string
8821
+ nullable: true
8822
+ assignedTo:
8823
+ type: string
8824
+ nullable: true
8825
+ identityVerifiedAt:
8826
+ type: string
8827
+ format: date-time
8828
+ nullable: true
8829
+ verifiedBy:
8830
+ type: string
8831
+ nullable: true
8832
+ fulfillmentAction:
8833
+ type: string
8834
+ nullable: true
8835
+ fulfillmentRef:
8836
+ type: string
8837
+ nullable: true
8838
+ slaDueAt:
8839
+ type: string
8840
+ format: date-time
8841
+ completedAt:
8842
+ type: string
8843
+ format: date-time
8844
+ nullable: true
8845
+ createdAt:
8846
+ type: string
8847
+ format: date-time
8848
+ updatedAt:
8849
+ type: string
8850
+ format: date-time
8851
+ overdue:
8852
+ type: boolean
8853
+ description: True when the SLA due date has passed and the request is not completed/rejected.
8854
+
8855
+ PrivacyRequestEventDto:
8856
+ type: object
8857
+ required: [id, eventType, createdAt]
8858
+ properties:
8859
+ id:
8860
+ type: string
8861
+ format: uuid
8862
+ eventType:
8863
+ type: string
8864
+ enum:
8865
+ [submitted, identity_verified, status_changed, assigned, export_generated, fulfillment_noted, duplicate_rejected]
8866
+ actorEmail:
8867
+ type: string
8868
+ nullable: true
8869
+ payload:
8870
+ type: object
8871
+ additionalProperties: true
8872
+ nullable: true
8873
+ createdAt:
8874
+ type: string
8875
+ format: date-time
8876
+
8877
+ PrivacyRequestDetailDto:
8878
+ type: object
8879
+ required: [request, events]
8880
+ properties:
8881
+ request:
8882
+ $ref: "#/components/schemas/PrivacyRequestDto"
8883
+ events:
8884
+ type: array
8885
+ items:
8886
+ $ref: "#/components/schemas/PrivacyRequestEventDto"
8887
+
8888
+ PrivacyRequestListDto:
8889
+ type: object
8890
+ required: [requests]
8891
+ properties:
8892
+ requests:
8893
+ type: array
8894
+ items:
8895
+ $ref: "#/components/schemas/PrivacyRequestDto"
8896
+
8897
+ UpdatePrivacyRequestBodyDto:
8898
+ type: object
8899
+ properties:
8900
+ status:
8901
+ $ref: "#/components/schemas/PrivacyRequestStatus"
8902
+ assignedTo:
8903
+ type: string
8904
+ nullable: true
8905
+ internalNotes:
8906
+ type: string
8907
+ nullable: true
8908
+ markIdentityVerified:
8909
+ type: boolean
8910
+ description: When true, records the caller as the verifier and unlocks export/erasure.
8911
+ fulfillmentAction:
8912
+ type: string
8913
+ description: Short label for how the request was fulfilled (required when completing).
8914
+ fulfillmentRef:
8915
+ type: string
8916
+ description: Reference to the fulfillment artifact (e.g. deletion job id; required when completing).
8917
+
8918
+ FulfillPersonErasureBodyDto:
8919
+ type: object
8920
+ required: [workspaceId, personId]
8921
+ properties:
8922
+ workspaceId:
8923
+ type: string
8924
+ format: uuid
8925
+ personId:
8926
+ type: string
8927
+ description: Org-chart person id (usually the subject's email) within the workspace.
8928
+ orgChart:
8929
+ type: boolean
8930
+ dashboardMetrics:
8931
+ type: boolean
8932
+ aiToolData:
8933
+ type: boolean
8934
+ absences:
8935
+ type: boolean
8936
+ readinessAnonymize:
8937
+ type: boolean
8938
+
8939
+ PrivacyRequestPersonErasureResultDto:
8940
+ type: object
8941
+ required: [deleted]
8942
+ properties:
8943
+ deleted:
8944
+ type: boolean
8945
+ aiToolRowsDeleted:
8946
+ type: integer
8947
+ absencesDeleted:
8948
+ type: integer
8949
+ readinessRunsAnonymized:
8950
+ type: integer
8951
+ auditEntriesAnonymized:
8952
+ type: integer
8953
+
8954
+ PrivacyRequestExportDto:
8955
+ type: object
8956
+ required: [metadata, subject]
8957
+ description: >
8958
+ DSAR export bundle assembled on demand for an access/portability request. `sections` holds
8959
+ the subject's data grouped by source; shapes vary by section, so values are opaque objects.
8960
+ properties:
8961
+ metadata:
8962
+ type: object
8963
+ required: [referenceCode, generatedAt, subjectEmail]
8964
+ properties:
8965
+ referenceCode:
8966
+ type: string
8967
+ generatedAt:
8968
+ type: string
8969
+ format: date-time
8970
+ subjectEmail:
8971
+ type: string
8972
+ subjectUserId:
8973
+ type: string
8974
+ nullable: true
8975
+ workspaceCount:
8976
+ type: integer
8977
+ excluded:
8978
+ type: array
8979
+ items:
8980
+ type: string
8981
+ description: Data categories intentionally excluded from the bundle.
8982
+ readinessRedaction:
8983
+ type: string
8984
+ description: Redaction policy applied to readiness runs (e.g. `requester_runs_only`).
8985
+ subject:
8986
+ type: object
8987
+ additionalProperties: true
8988
+ description: Profile and consent records for the subject.
8989
+ sections:
8990
+ type: object
8991
+ additionalProperties: true
8992
+ description: Per-source personal data (memberships, AI-tool, dashboard, absences, org chart, activity).
8993
+ residualPersonData:
8994
+ type: array
8995
+ items:
8996
+ type: object
8997
+ additionalProperties: true
8998
+ description: Workspaces where person-keyed rows exist but the subject has no membership (#269).
8999
+
8003
9000
  WorkspaceSummaryDto:
8004
9001
  type: object
8005
9002
  required: [id, slug, name, status, role]