Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/backend-delete-invitation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/backend': minor
---

Add `clerkClient.invitations.deleteInvitation(invitationId)`, which permanently deletes an instance invitation and the stored copies of its invitation email. Unlike `revokeInvitation`, this removes the invitation record itself, so it can be used to honor a data erasure request from someone who was invited but never signed up.
35 changes: 35 additions & 0 deletions packages/backend/src/api/__tests__/InvitationApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { http, HttpResponse } from 'msw';
import { describe, expect, it } from 'vitest';

import { server, validateHeaders } from '../../mock-server';
import { createBackendApiClient } from '../factory';

describe('InvitationAPI', () => {
const apiClient = createBackendApiClient({
apiUrl: 'https://api.clerk.test',
secretKey: 'deadbeef',
});

describe('deleteInvitation', () => {
const invitationId = 'inv_123';

it('deletes an invitation by ID', async () => {
server.use(
http.delete(
`https://api.clerk.test/v1/invitations/${invitationId}`,
validateHeaders(() => HttpResponse.json({ object: 'invitation', id: invitationId, deleted: true })),
),
);

const response = await apiClient.invitations.deleteInvitation(invitationId);

expect(response.object).toBe('invitation');
expect(response.id).toBe(invitationId);
expect(response.deleted).toBe(true);
});

it('throws an error when the invitation ID is missing', async () => {
await expect(apiClient.invitations.deleteInvitation('')).rejects.toThrow('A valid resource ID is required.');
});
});
});
18 changes: 18 additions & 0 deletions packages/backend/src/api/endpoints/InvitationApi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ClerkPaginationRequest } from '@clerk/shared/types';

import { joinPaths } from '../../util/path';
import type { DeletedObject } from '../resources/DeletedObject';
import type { PaginatedResourceResponse } from '../resources/Deserializer';
import type { InvitationStatus } from '../resources/Enums';
import type { Invitation } from '../resources/Invitation';
Expand Down Expand Up @@ -132,4 +133,21 @@ export class InvitationAPI extends AbstractAPI {
path: joinPaths(basePath, invitationId, 'revoke'),
});
}

/**
* Permanently deletes the given invitation and the copies of the invitation email Clerk stored for its recipient.
*
* Unlike revoking, deleting removes the invitation record itself, which helps honor a data erasure request from someone who was invited but never signed up. Other records that contain the same email address, such as users or organization invitations, are not affected.
*
* Invitations of any status can be deleted.
* @param invitationId - The ID of the invitation to delete.
* @returns The [`DeletedObject`](https://clerk.com/docs/reference/backend/types/deleted-object) object.
*/
public async deleteInvitation(invitationId: string): Promise<DeletedObject> {
this.requireId(invitationId);
return this.request<DeletedObject>({
method: 'DELETE',
path: joinPaths(basePath, invitationId),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
}
}
Loading