Skip to content
Open
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
41 changes: 41 additions & 0 deletions .changeset/stable-api-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
"@clerk/shared": minor
"@clerk/react": minor
"@clerk/clerk-js": minor
"@clerk/ui": minor
---

API keys is now generally available.

### `<APIKeys />` component

```tsx
import { APIKeys } from '@clerk/react';

export default function Page() {
return <APIKeys />;
}
```

### `useAPIKeys()` hook

```tsx
import { useAPIKeys } from '@clerk/react';

export default function CustomAPIKeys() {
const { data, isLoading, page, pageCount, fetchNext, fetchPrevious } = useAPIKeys({
pageSize: 10,
initialPage: 1,
});

if (isLoading) return <div>Loading...</div>;

return (
<ul>
{data?.map((key) => (
<li key={key.id}>{key.name}</li>
))}
</ul>
);
}
```
10 changes: 2 additions & 8 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1332,15 +1332,11 @@ export class Clerk implements ClerkInterface {
};

/**
* @experimental This API is in early access and may change in future releases.
*
* Mount a API keys component at the target element.
* Mount an API keys component at the target element.
* @param targetNode Target to mount the APIKeys component.
* @param props Configuration parameters.
*/
public mountAPIKeys = (node: HTMLDivElement, props?: APIKeysProps) => {
logger.warnOnce('Clerk: <APIKeys /> component is in early access and not yet recommended for production use.');

if (disabledAllAPIKeysFeatures(this, this.environment)) {
if (this.#instanceType === 'development') {
throw new ClerkRuntimeError(warnings.cannotRenderAPIKeysComponent, {
Expand Down Expand Up @@ -1385,9 +1381,7 @@ export class Clerk implements ClerkInterface {
};

/**
* @experimental This API is in early access and may change in future releases.
*
* Unmount a API keys component from the target element.
* Unmount an API keys component from the target element.
* If there is no component mounted at the target node, results in a noop.
*
* @param targetNode Target node to unmount the APIKeys component from.
Expand Down
26 changes: 18 additions & 8 deletions packages/clerk-js/src/core/modules/apiKeys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import { convertPageToOffsetSearchParams } from '@/utils/convertPageToOffsetSear
import { APIKey, BaseResource } from '../../resources/internal';

export class APIKeys implements APIKeysNamespace {
static readonly #pathRoot = '/api_keys';

/**
* Returns the base options for the FAPI proxy requests.
*/
private async getBaseFapiProxyOptions(): Promise<FapiRequestInit> {
async #getBaseFapiProxyOptions(): Promise<FapiRequestInit> {
const token = await BaseResource.clerk.session?.getToken();
if (!token) {
throw new ClerkRuntimeError('No valid session token available', { code: 'no_session_token' });
Expand All @@ -27,7 +29,7 @@ export class APIKeys implements APIKeysNamespace {
return {
// Set to an empty string because FAPI Proxy does not include the version in the path.
pathPrefix: '',
// Set the session token as a Bearer token in the Authorization header for authentication.
// FAPI Proxy looks for the session token in the Authorization header.
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Expand All @@ -37,11 +39,19 @@ export class APIKeys implements APIKeysNamespace {
};
}

/**
* Retrieves a paginated list of API keys.
*
* The subject (owner) is resolved in the following order:
* 1. Explicit `subject` param (user or organization ID)
* 2. Active organization ID
* 3. Current user ID
*/
async getAll(params?: GetAPIKeysParams): Promise<ClerkPaginatedResponse<APIKeyResource>> {
return BaseResource._fetch({
...(await this.getBaseFapiProxyOptions()),
...(await this.#getBaseFapiProxyOptions()),
method: 'GET',
path: '/api_keys',
path: APIKeys.#pathRoot,
search: convertPageToOffsetSearchParams({
...params,
subject: params?.subject ?? BaseResource.clerk.organization?.id ?? BaseResource.clerk.user?.id ?? '',
Expand All @@ -59,8 +69,8 @@ export class APIKeys implements APIKeysNamespace {

async create(params: CreateAPIKeyParams): Promise<APIKeyResource> {
const json = (await BaseResource._fetch<ApiKeyJSON>({
...(await this.getBaseFapiProxyOptions()),
path: '/api_keys',
...(await this.#getBaseFapiProxyOptions()),
path: APIKeys.#pathRoot,
method: 'POST',
body: JSON.stringify({
type: 'api_key',
Expand All @@ -76,9 +86,9 @@ export class APIKeys implements APIKeysNamespace {

async revoke(params: RevokeAPIKeyParams): Promise<APIKeyResource> {
const json = (await BaseResource._fetch<ApiKeyJSON>({
...(await this.getBaseFapiProxyOptions()),
...(await this.#getBaseFapiProxyOptions()),
method: 'POST',
path: `/api_keys/${params.apiKeyID}/revoke`,
path: `${APIKeys.#pathRoot}/${params.apiKeyID}/revoke`,
body: JSON.stringify({
revocation_reason: params.revocationReason,
}),
Expand Down
1 change: 0 additions & 1 deletion packages/react/src/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export type {
} from '@clerk/shared/types';

export {
__experimental_useAPIKeys as useAPIKeys,
__experimental_PaymentElementProvider as PaymentElementProvider,
__experimental_usePaymentElement as usePaymentElement,
__experimental_PaymentElement as PaymentElement,
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {
useUser,
useSession,
useReverification,
useAPIKeys,
__experimental_useCheckout,
__experimental_CheckoutProvider,
__experimental_usePaymentElement,
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/react/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { assertContextExists, createContextAndHook } from './createContextAndHook';
export { useAPIKeys as __experimental_useAPIKeys } from './useAPIKeys';
export { useAPIKeys } from './useAPIKeys';
export { useOrganization } from './useOrganization';
export { useOrganizationCreationDefaults } from './useOrganizationCreationDefaults';
export type {
Expand Down
6 changes: 2 additions & 4 deletions packages/shared/src/react/hooks/useAPIKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createCacheKeys } from './createCacheKeys';
import { usePagesOrInfinite, useWithSafeValues } from './usePagesOrInfinite';

/**
* @internal
* @interface
*/
export type UseAPIKeysParams = PaginatedHookConfig<
GetAPIKeysParams & {
Expand All @@ -21,16 +21,14 @@ export type UseAPIKeysParams = PaginatedHookConfig<
>;

/**
* @internal
* @interface
*/
export type UseAPIKeysReturn<T extends UseAPIKeysParams> = PaginatedResources<
APIKeyResource,
T extends { infinite: true } ? true : false
>;

/**
* @internal
*
* The `useAPIKeys()` hook provides access to paginated API keys for the current user or organization.
*
* @example
Expand Down
6 changes: 0 additions & 6 deletions packages/shared/src/types/apiKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,14 @@ export interface APIKeyResource extends ClerkResource {

export interface APIKeysNamespace {
/**
* @experimental This API is in early access and may change in future releases.
*
* Retrieves a paginated list of API keys for the current user or organization.
*/
getAll(params?: GetAPIKeysParams): Promise<ClerkPaginatedResponse<APIKeyResource>>;
/**
* @experimental This API is in early access and may change in future releases.
*
* Creates a new API key.
*/
create(params: CreateAPIKeyParams): Promise<APIKeyResource>;
/**
* @experimental This API is in early access and may change in future releases.
*
* Revokes a given API key by ID.
*/
revoke(params: RevokeAPIKeyParams): Promise<APIKeyResource>;
Expand Down
17 changes: 3 additions & 14 deletions packages/shared/src/types/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,26 +644,18 @@ export interface Clerk {
unmountPricingTable: (targetNode: HTMLDivElement) => void;

/**
* This API is in early access and may change in future releases.
*
* Mount a api keys component at the target element.
*
* @experimental
* Mount an API keys component at the target element.
*
* @param targetNode - Target to mount the APIKeys component.
* @param props - Configuration parameters.
*/
mountAPIKeys: (targetNode: HTMLDivElement, props?: APIKeysProps) => void;

/**
* This API is in early access and may change in future releases.
*
* Unmount a api keys component from the target element.
* Unmount an API keys component from the target element.
* If there is no component mounted at the target node, results in a noop.
*
* @experimental
*
* @param targetNode - Target node to unmount the ApiKeys component from.
* @param targetNode - Target node to unmount the APIKeys component from.
*/
unmountAPIKeys: (targetNode: HTMLDivElement) => void;

Expand Down Expand Up @@ -1032,9 +1024,6 @@ export interface Clerk {

/**
* API Keys Object
*
* @experimental
* This API is in early access and may change in future releases.
*/
apiKeys: APIKeysNamespace;

Expand Down
7 changes: 1 addition & 6 deletions packages/ui/src/components/APIKeys/APIKeys.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { isClerkAPIResponseError } from '@clerk/shared/error';
import { isOrganizationId } from '@clerk/shared/internal/clerk-js/organization';
import {
__experimental_useAPIKeys as useAPIKeys,
__internal_useOrganizationBase,
useClerk,
useUser,
} from '@clerk/shared/react';
import { useAPIKeys, __internal_useOrganizationBase, useClerk, useUser } from '@clerk/shared/react';
import type { APIKeyResource } from '@clerk/shared/types';
import { lazy, useState } from 'react';

Expand Down
Loading