@thinkai/tai-api-contract 2.70.0 → 2.71.0

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.
@@ -355,7 +355,7 @@ export interface paths {
355
355
  put?: never;
356
356
  /**
357
357
  * Invite member to workspace
358
- * @description Workspace admin only. Upserts `tai_users` by email and adds or reactivates `tai_workspace_members`. Idempotent when the email is already an active member: returns **201** with the existing row and **does not change** their role if the request body role differs. Does not send email; optional `tai_workspace_invitations` row may be recorded (no accept/link flow yet).
358
+ * @description Workspace admin only. Creates or refreshes a **pending** `tai_workspace_invitations` row and emails an accept link; it does **not** create `tai_workspace_members`. Membership is granted only by `POST /invitations/{token}/accept`. Applies to new and existing platform users alike. Idempotent when the email is already an active member: returns **201** with `outcome: "already_member"` and the existing row, **does not change** their role if the request body role differs, and sends no email. Re-inviting a pending email refreshes the token and 7-day expiry (the previous link stops working). When SMTP is not configured the invitation is still created and a warning is logged.
359
359
  */
360
360
  post: operations["inviteTenantMember"];
361
361
  delete?: never;
@@ -388,6 +388,86 @@ export interface paths {
388
388
  patch: operations["updateTenantMemberRole"];
389
389
  trace?: never;
390
390
  };
391
+ "/workspaces/{workspaceId}/invitations": {
392
+ parameters: {
393
+ query?: never;
394
+ header?: never;
395
+ path?: never;
396
+ cookie?: never;
397
+ };
398
+ /**
399
+ * List pending workspace invitations
400
+ * @description Workspace admin only. Returns invitations still awaiting a decision so the team settings page can show "pending" alongside active members. Rows whose `expiresAt` has passed are flipped to `expired` on read and omitted.
401
+ */
402
+ get: operations["listWorkspaceInvitations"];
403
+ put?: never;
404
+ post?: never;
405
+ delete?: never;
406
+ options?: never;
407
+ head?: never;
408
+ patch?: never;
409
+ trace?: never;
410
+ };
411
+ "/invitations/{token}": {
412
+ parameters: {
413
+ query?: never;
414
+ header?: never;
415
+ path?: never;
416
+ cookie?: never;
417
+ };
418
+ /**
419
+ * Preview a workspace invitation (public)
420
+ * @description **Unauthenticated.** Lets the SPA render the invite landing page before the invitee has signed in. The invited email is **masked** (`j••••e@example.com`) so a leaked link does not disclose an address. Returns `404` with a generic message for unknown, revoked, or rejected tokens; a past-due `pending` invitation is flipped to `expired` and returned with that status so the SPA can show a stable message.
421
+ */
422
+ get: operations["getWorkspaceInvitationPreview"];
423
+ put?: never;
424
+ post?: never;
425
+ delete?: never;
426
+ options?: never;
427
+ head?: never;
428
+ patch?: never;
429
+ trace?: never;
430
+ };
431
+ "/invitations/{token}/accept": {
432
+ parameters: {
433
+ query?: never;
434
+ header?: never;
435
+ path?: never;
436
+ cookie?: never;
437
+ };
438
+ get?: never;
439
+ put?: never;
440
+ /**
441
+ * Accept a workspace invitation
442
+ * @description Requires a Supabase JWT whose email matches the invited email. Creates the `tai_workspace_members` row with the invited role and closes the invitation as `accepted`. This is the **only** path that grants workspace membership — signing up alone never does. Idempotent: re-accepting your own accepted invitation returns `200` unchanged.
443
+ */
444
+ post: operations["acceptWorkspaceInvitation"];
445
+ delete?: never;
446
+ options?: never;
447
+ head?: never;
448
+ patch?: never;
449
+ trace?: never;
450
+ };
451
+ "/invitations/{token}/reject": {
452
+ parameters: {
453
+ query?: never;
454
+ header?: never;
455
+ path?: never;
456
+ cookie?: never;
457
+ };
458
+ get?: never;
459
+ put?: never;
460
+ /**
461
+ * Decline a workspace invitation
462
+ * @description Requires a Supabase JWT whose email matches the invited email — anonymous decline with only the token is not allowed. Closes the invitation as `rejected` and creates no member row. Idempotent for an already-rejected invitation.
463
+ */
464
+ post: operations["rejectWorkspaceInvitation"];
465
+ delete?: never;
466
+ options?: never;
467
+ head?: never;
468
+ patch?: never;
469
+ trace?: never;
470
+ };
391
471
  "/workspaces/{workspaceId}/org-chart/status": {
392
472
  parameters: {
393
473
  query?: never;
@@ -3098,6 +3178,54 @@ export interface components {
3098
3178
  /** @enum {string} */
3099
3179
  role: "admin" | "editor" | "viewer";
3100
3180
  };
3181
+ /**
3182
+ * @description `revoked` is an admin cancelling the invitation; `rejected` is the invitee declining it. Both leave the invitee outside the workspace.
3183
+ * @enum {string}
3184
+ */
3185
+ WorkspaceInvitationStatusDto: "pending" | "accepted" | "expired" | "revoked" | "rejected";
3186
+ /** @description Workspace invitation as seen by an authenticated admin or invitee. */
3187
+ WorkspaceInvitationDto: {
3188
+ /** Format: uuid */
3189
+ id: string;
3190
+ /** Format: uuid */
3191
+ workspaceId: string;
3192
+ workspaceName: string;
3193
+ /** @description Invited email address. */
3194
+ email: string;
3195
+ /** @enum {string} */
3196
+ role: "admin" | "editor" | "viewer";
3197
+ status: components["schemas"]["WorkspaceInvitationStatusDto"];
3198
+ invitedByName?: string | null;
3199
+ invitedByEmail?: string | null;
3200
+ /** Format: date-time */
3201
+ expiresAt: string;
3202
+ /** Format: date-time */
3203
+ createdAt: string;
3204
+ /** Format: date-time */
3205
+ acceptedAt?: string | null;
3206
+ };
3207
+ /** @description Unauthenticated invitation preview. `maskedEmail` is the only view of the invited address; compare it to the signed-in account client-side before calling accept. */
3208
+ WorkspaceInvitationPreviewDto: {
3209
+ workspaceName: string;
3210
+ /** @enum {string} */
3211
+ role: "admin" | "editor" | "viewer";
3212
+ status: components["schemas"]["WorkspaceInvitationStatusDto"];
3213
+ /** @example j••••e@example.com */
3214
+ maskedEmail: string;
3215
+ invitedByName?: string | null;
3216
+ /** Format: date-time */
3217
+ expiresAt: string;
3218
+ };
3219
+ WorkspaceInvitationListDto: {
3220
+ invitations: components["schemas"]["WorkspaceInvitationDto"][];
3221
+ };
3222
+ /** @description Discriminated on `outcome`. `invited` carries the new pending invitation; `already_member` carries the existing membership row unchanged. */
3223
+ InviteTeamMemberResultDto: {
3224
+ /** @enum {string} */
3225
+ outcome: "invited" | "already_member";
3226
+ invitation?: components["schemas"]["WorkspaceInvitationDto"];
3227
+ member?: components["schemas"]["TeamMemberDto"];
3228
+ };
3101
3229
  /** @description Granular person erasure categories. All default to true when omitted. */
3102
3230
  EraseInsightsPersonBodyDto: {
3103
3231
  /** @description Remove the person node from `tai_workspace_config.org_chart`. */
@@ -6215,7 +6343,7 @@ export interface components {
6215
6343
  * @description Type of workspace lifecycle event.
6216
6344
  * @enum {string}
6217
6345
  */
6218
- ActivityLogEventTypeDto: "repo.added" | "repo.removed" | "repo.readded" | "integration.github.added" | "integration.github.updated" | "integration.github.removed" | "integration.github.permissions_changed" | "integration.github.repo_added" | "integration.github.repo_removed" | "fix.requested" | "fix.generating" | "fix.pr_created" | "fix.merged" | "fix.failed" | "readiness.scan_started" | "readiness.scan_completed" | "readiness.scan_failed" | "readiness.scan_partial" | "integrations.agent_execution.provider_changed" | "integrations.repo_eligibility.overrides_changed" | "integrations.repo_eligibility.languages_refresh_requested" | "integrations.cursor.platform_key_enabled" | "integrations.cursor.platform_key_disabled" | "member.invited" | "member.role_changed" | "member.removed" | "hris.personio.sync_started" | "hris.personio.sync_completed" | "hris.personio.sync_failed" | "hris.zenhr.sync_started" | "hris.zenhr.sync_completed" | "hris.zenhr.sync_failed" | "hris.zelt.sync_started" | "hris.zelt.sync_completed" | "hris.zelt.sync_failed" | "hris.sync_started" | "hris.sync_completed" | "hris.sync_failed" | "repo.sync_skipped" | "integrations.cursor.execution_key.added" | "integrations.cursor.execution_key.updated" | "integrations.cursor.execution_key.removed" | "integrations.cursor.execution_key.check_failed" | "integrations.cursor.admin_key.added" | "integrations.cursor.admin_key.updated" | "integrations.cursor.admin_key.removed" | "integrations.claude.execution_key.added" | "integrations.claude.execution_key.updated" | "integrations.claude.execution_key.removed" | "integrations.claude.execution_key.check_failed" | "integrations.claude.admin_key.added" | "integrations.claude.admin_key.updated" | "integrations.claude.admin_key.removed" | "integrations.claude.analytics_key.added" | "integrations.claude.analytics_key.updated" | "integrations.claude.analytics_key.removed" | "readiness.scan_precheck_failed" | "productivity.ingest.completed" | "productivity.ingest.failed" | "productivity.ingest.skipped" | "productivity.ingest.repo_skipped" | "productivity.ingest.coverage_gap_detected" | "integrations.newrelic.added" | "integrations.newrelic.removed" | "integrations.newrelic.sync_completed" | "integrations.newrelic.sync_failed" | "integrations.prometheus.added" | "integrations.prometheus.removed" | "integrations.prometheus.sync_completed" | "integrations.prometheus.sync_failed" | "integrations.grafana.added" | "integrations.grafana.removed" | "integrations.grafana.sync_completed" | "integrations.grafana.sync_failed" | "integrations.zelt.added" | "integrations.zelt.removed";
6346
+ ActivityLogEventTypeDto: "repo.added" | "repo.removed" | "repo.readded" | "integration.github.added" | "integration.github.updated" | "integration.github.removed" | "integration.github.permissions_changed" | "integration.github.repo_added" | "integration.github.repo_removed" | "fix.requested" | "fix.generating" | "fix.pr_created" | "fix.merged" | "fix.failed" | "readiness.scan_started" | "readiness.scan_completed" | "readiness.scan_failed" | "readiness.scan_partial" | "integrations.agent_execution.provider_changed" | "integrations.repo_eligibility.overrides_changed" | "integrations.repo_eligibility.languages_refresh_requested" | "integrations.cursor.platform_key_enabled" | "integrations.cursor.platform_key_disabled" | "member.invited" | "member.invite_accepted" | "member.invite_rejected" | "member.role_changed" | "member.removed" | "hris.personio.sync_started" | "hris.personio.sync_completed" | "hris.personio.sync_failed" | "hris.zenhr.sync_started" | "hris.zenhr.sync_completed" | "hris.zenhr.sync_failed" | "hris.zelt.sync_started" | "hris.zelt.sync_completed" | "hris.zelt.sync_failed" | "hris.sync_started" | "hris.sync_completed" | "hris.sync_failed" | "repo.sync_skipped" | "integrations.cursor.execution_key.added" | "integrations.cursor.execution_key.updated" | "integrations.cursor.execution_key.removed" | "integrations.cursor.execution_key.check_failed" | "integrations.cursor.admin_key.added" | "integrations.cursor.admin_key.updated" | "integrations.cursor.admin_key.removed" | "integrations.claude.execution_key.added" | "integrations.claude.execution_key.updated" | "integrations.claude.execution_key.removed" | "integrations.claude.execution_key.check_failed" | "integrations.claude.admin_key.added" | "integrations.claude.admin_key.updated" | "integrations.claude.admin_key.removed" | "integrations.claude.analytics_key.added" | "integrations.claude.analytics_key.updated" | "integrations.claude.analytics_key.removed" | "readiness.scan_precheck_failed" | "productivity.ingest.completed" | "productivity.ingest.failed" | "productivity.ingest.skipped" | "productivity.ingest.repo_skipped" | "productivity.ingest.coverage_gap_detected" | "integrations.newrelic.added" | "integrations.newrelic.removed" | "integrations.newrelic.sync_completed" | "integrations.newrelic.sync_failed" | "integrations.prometheus.added" | "integrations.prometheus.removed" | "integrations.prometheus.sync_completed" | "integrations.prometheus.sync_failed" | "integrations.grafana.added" | "integrations.grafana.removed" | "integrations.grafana.sync_completed" | "integrations.grafana.sync_failed" | "integrations.zelt.added" | "integrations.zelt.removed";
6219
6347
  ActivityLogActorDto: {
6220
6348
  /** Format: uuid */
6221
6349
  userId: string;
@@ -6770,6 +6898,8 @@ export interface components {
6770
6898
  NotificationId: string;
6771
6899
  /** @description Workspace member user id (`tai_users.id`). */
6772
6900
  WorkspaceMemberId: string;
6901
+ /** @description Opaque workspace invitation token from the emailed accept link. Only its SHA-256 hash is stored server-side; the plaintext is shown exactly once, in the email. */
6902
+ WorkspaceInvitationToken: string;
6773
6903
  /** @description Privacy request id (`tai_privacy_requests.id`). */
6774
6904
  PrivacyRequestId: string;
6775
6905
  FixId: string;
@@ -6835,6 +6965,11 @@ export type TeamMemberDto = components['schemas']['TeamMemberDto'];
6835
6965
  export type TeamMemberListDto = components['schemas']['TeamMemberListDto'];
6836
6966
  export type InviteTeamMemberBodyDto = components['schemas']['InviteTeamMemberBodyDto'];
6837
6967
  export type UpdateTeamMemberRoleBodyDto = components['schemas']['UpdateTeamMemberRoleBodyDto'];
6968
+ export type WorkspaceInvitationStatusDto = components['schemas']['WorkspaceInvitationStatusDto'];
6969
+ export type WorkspaceInvitationDto = components['schemas']['WorkspaceInvitationDto'];
6970
+ export type WorkspaceInvitationPreviewDto = components['schemas']['WorkspaceInvitationPreviewDto'];
6971
+ export type WorkspaceInvitationListDto = components['schemas']['WorkspaceInvitationListDto'];
6972
+ export type InviteTeamMemberResultDto = components['schemas']['InviteTeamMemberResultDto'];
6838
6973
  export type EraseInsightsPersonBodyDto = components['schemas']['EraseInsightsPersonBodyDto'];
6839
6974
  export type EraseInsightsPersonResultDto = components['schemas']['EraseInsightsPersonResultDto'];
6840
6975
  export type OrgChartNodeDto = components['schemas']['OrgChartNodeDto'];
@@ -7181,6 +7316,7 @@ export type ParameterProviderSlug = components['parameters']['ProviderSlug'];
7181
7316
  export type ParameterProjectId = components['parameters']['ProjectId'];
7182
7317
  export type ParameterNotificationId = components['parameters']['NotificationId'];
7183
7318
  export type ParameterWorkspaceMemberId = components['parameters']['WorkspaceMemberId'];
7319
+ export type ParameterWorkspaceInvitationToken = components['parameters']['WorkspaceInvitationToken'];
7184
7320
  export type ParameterPrivacyRequestId = components['parameters']['PrivacyRequestId'];
7185
7321
  export type ParameterFixId = components['parameters']['FixId'];
7186
7322
  export type ParameterInsightsProductivityRangeId = components['parameters']['InsightsProductivityRangeId'];
@@ -8224,13 +8360,13 @@ export interface operations {
8224
8360
  };
8225
8361
  };
8226
8362
  responses: {
8227
- /** @description Invited member row */
8363
+ /** @description Pending invitation, or the existing member row when already a member */
8228
8364
  201: {
8229
8365
  headers: {
8230
8366
  [name: string]: unknown;
8231
8367
  };
8232
8368
  content: {
8233
- "application/json": components["schemas"]["TeamMemberDto"];
8369
+ "application/json": components["schemas"]["InviteTeamMemberResultDto"];
8234
8370
  };
8235
8371
  };
8236
8372
  /** @description Invalid email or role */
@@ -8359,6 +8495,191 @@ export interface operations {
8359
8495
  };
8360
8496
  };
8361
8497
  };
8498
+ listWorkspaceInvitations: {
8499
+ parameters: {
8500
+ query?: never;
8501
+ header?: never;
8502
+ path: {
8503
+ workspaceId: components["parameters"]["WorkspaceId"];
8504
+ };
8505
+ cookie?: never;
8506
+ };
8507
+ requestBody?: never;
8508
+ responses: {
8509
+ /** @description Pending invitations */
8510
+ 200: {
8511
+ headers: {
8512
+ [name: string]: unknown;
8513
+ };
8514
+ content: {
8515
+ "application/json": components["schemas"]["WorkspaceInvitationListDto"];
8516
+ };
8517
+ };
8518
+ 401: components["responses"]["Unauthorized"];
8519
+ 403: components["responses"]["Forbidden"];
8520
+ /** @description Workspace does not exist or malformed workspaceId */
8521
+ 404: {
8522
+ headers: {
8523
+ [name: string]: unknown;
8524
+ };
8525
+ content: {
8526
+ "application/json": components["schemas"]["ErrorMessageDto"];
8527
+ };
8528
+ };
8529
+ };
8530
+ };
8531
+ getWorkspaceInvitationPreview: {
8532
+ parameters: {
8533
+ query?: never;
8534
+ header?: never;
8535
+ path: {
8536
+ /** @description Opaque workspace invitation token from the emailed accept link. Only its SHA-256 hash is stored server-side; the plaintext is shown exactly once, in the email. */
8537
+ token: components["parameters"]["WorkspaceInvitationToken"];
8538
+ };
8539
+ cookie?: never;
8540
+ };
8541
+ requestBody?: never;
8542
+ responses: {
8543
+ /** @description Invitation preview */
8544
+ 200: {
8545
+ headers: {
8546
+ [name: string]: unknown;
8547
+ };
8548
+ content: {
8549
+ "application/json": components["schemas"]["WorkspaceInvitationPreviewDto"];
8550
+ };
8551
+ };
8552
+ /** @description Unknown or no longer available invitation */
8553
+ 404: {
8554
+ headers: {
8555
+ [name: string]: unknown;
8556
+ };
8557
+ content: {
8558
+ "application/json": components["schemas"]["ErrorMessageDto"];
8559
+ };
8560
+ };
8561
+ };
8562
+ };
8563
+ acceptWorkspaceInvitation: {
8564
+ parameters: {
8565
+ query?: never;
8566
+ header?: never;
8567
+ path: {
8568
+ /** @description Opaque workspace invitation token from the emailed accept link. Only its SHA-256 hash is stored server-side; the plaintext is shown exactly once, in the email. */
8569
+ token: components["parameters"]["WorkspaceInvitationToken"];
8570
+ };
8571
+ cookie?: never;
8572
+ };
8573
+ requestBody?: never;
8574
+ responses: {
8575
+ /** @description Invitation accepted; caller is now a workspace member */
8576
+ 200: {
8577
+ headers: {
8578
+ [name: string]: unknown;
8579
+ };
8580
+ content: {
8581
+ "application/json": components["schemas"]["WorkspaceInvitationDto"];
8582
+ };
8583
+ };
8584
+ 401: components["responses"]["Unauthorized"];
8585
+ /** @description Signed-in email does not match the invited email */
8586
+ 403: {
8587
+ headers: {
8588
+ [name: string]: unknown;
8589
+ };
8590
+ content: {
8591
+ "application/json": components["schemas"]["ErrorMessageDto"];
8592
+ };
8593
+ };
8594
+ /** @description Unknown invitation token */
8595
+ 404: {
8596
+ headers: {
8597
+ [name: string]: unknown;
8598
+ };
8599
+ content: {
8600
+ "application/json": components["schemas"]["ErrorMessageDto"];
8601
+ };
8602
+ };
8603
+ /** @description Invitation is no longer actionable */
8604
+ 409: {
8605
+ headers: {
8606
+ [name: string]: unknown;
8607
+ };
8608
+ content: {
8609
+ "application/json": components["schemas"]["ErrorMessageDto"];
8610
+ };
8611
+ };
8612
+ /** @description Invitation expired */
8613
+ 410: {
8614
+ headers: {
8615
+ [name: string]: unknown;
8616
+ };
8617
+ content: {
8618
+ "application/json": components["schemas"]["ErrorMessageDto"];
8619
+ };
8620
+ };
8621
+ };
8622
+ };
8623
+ rejectWorkspaceInvitation: {
8624
+ parameters: {
8625
+ query?: never;
8626
+ header?: never;
8627
+ path: {
8628
+ /** @description Opaque workspace invitation token from the emailed accept link. Only its SHA-256 hash is stored server-side; the plaintext is shown exactly once, in the email. */
8629
+ token: components["parameters"]["WorkspaceInvitationToken"];
8630
+ };
8631
+ cookie?: never;
8632
+ };
8633
+ requestBody?: never;
8634
+ responses: {
8635
+ /** @description Invitation declined */
8636
+ 200: {
8637
+ headers: {
8638
+ [name: string]: unknown;
8639
+ };
8640
+ content: {
8641
+ "application/json": components["schemas"]["WorkspaceInvitationDto"];
8642
+ };
8643
+ };
8644
+ 401: components["responses"]["Unauthorized"];
8645
+ /** @description Signed-in email does not match the invited email */
8646
+ 403: {
8647
+ headers: {
8648
+ [name: string]: unknown;
8649
+ };
8650
+ content: {
8651
+ "application/json": components["schemas"]["ErrorMessageDto"];
8652
+ };
8653
+ };
8654
+ /** @description Unknown invitation token */
8655
+ 404: {
8656
+ headers: {
8657
+ [name: string]: unknown;
8658
+ };
8659
+ content: {
8660
+ "application/json": components["schemas"]["ErrorMessageDto"];
8661
+ };
8662
+ };
8663
+ /** @description Invitation was already accepted */
8664
+ 409: {
8665
+ headers: {
8666
+ [name: string]: unknown;
8667
+ };
8668
+ content: {
8669
+ "application/json": components["schemas"]["ErrorMessageDto"];
8670
+ };
8671
+ };
8672
+ /** @description Invitation expired */
8673
+ 410: {
8674
+ headers: {
8675
+ [name: string]: unknown;
8676
+ };
8677
+ content: {
8678
+ "application/json": components["schemas"]["ErrorMessageDto"];
8679
+ };
8680
+ };
8681
+ };
8682
+ };
8362
8683
  getWorkspaceOrgChartStatus: {
8363
8684
  parameters: {
8364
8685
  query?: never;