feat(platform) : Add api key generation frontend (#9212)
Allow users to create API keys for the AutoGPT platform. The backend is already set up, and here I’ve added the frontend for it. ### Changes 1. Fix the `response-model` of the API keys endpoints. 2. Add a new page `/store/api_keys`. 3. Add an `APIKeySection` component to create, delete, and view all your API keys. <img width="1512" alt="Screenshot 2025-01-07 at 3 59 25 PM" src="https://github.com/user-attachments/assets/ea4e9d35-eb92-4e10-a4fb-1fc51dfe11bb" />
This commit is contained in:
parent
7ec9830b02
commit
43a79d063f
|
@ -541,7 +541,7 @@ def get_execution_schedules(
|
||||||
|
|
||||||
@v1_router.post(
|
@v1_router.post(
|
||||||
"/api-keys",
|
"/api-keys",
|
||||||
response_model=list[CreateAPIKeyResponse] | dict[str, str],
|
response_model=CreateAPIKeyResponse,
|
||||||
tags=["api-keys"],
|
tags=["api-keys"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
|
@ -583,7 +583,7 @@ async def get_api_keys(
|
||||||
|
|
||||||
@v1_router.get(
|
@v1_router.get(
|
||||||
"/api-keys/{key_id}",
|
"/api-keys/{key_id}",
|
||||||
response_model=list[APIKeyWithoutHash] | dict[str, str],
|
response_model=APIKeyWithoutHash,
|
||||||
tags=["api-keys"],
|
tags=["api-keys"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
|
@ -604,7 +604,7 @@ async def get_api_key(
|
||||||
|
|
||||||
@v1_router.delete(
|
@v1_router.delete(
|
||||||
"/api-keys/{key_id}",
|
"/api-keys/{key_id}",
|
||||||
response_model=list[APIKeyWithoutHash] | dict[str, str],
|
response_model=APIKeyWithoutHash,
|
||||||
tags=["api-keys"],
|
tags=["api-keys"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
|
@ -626,7 +626,7 @@ async def delete_api_key(
|
||||||
|
|
||||||
@v1_router.post(
|
@v1_router.post(
|
||||||
"/api-keys/{key_id}/suspend",
|
"/api-keys/{key_id}/suspend",
|
||||||
response_model=list[APIKeyWithoutHash] | dict[str, str],
|
response_model=APIKeyWithoutHash,
|
||||||
tags=["api-keys"],
|
tags=["api-keys"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
|
@ -648,7 +648,7 @@ async def suspend_key(
|
||||||
|
|
||||||
@v1_router.put(
|
@v1_router.put(
|
||||||
"/api-keys/{key_id}/permissions",
|
"/api-keys/{key_id}/permissions",
|
||||||
response_model=list[APIKeyWithoutHash] | dict[str, str],
|
response_model=APIKeyWithoutHash,
|
||||||
tags=["api-keys"],
|
tags=["api-keys"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { APIKeysSection } from "@/components/agptui/composite/APIKeySection";
|
||||||
|
|
||||||
|
const ApiKeysPage = () => {
|
||||||
|
return (
|
||||||
|
<div className="w-full pr-4 pt-24 md:pt-0">
|
||||||
|
<APIKeysSection />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApiKeysPage;
|
|
@ -8,6 +8,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
{ text: "Creator Dashboard", href: "/store/dashboard" },
|
{ text: "Creator Dashboard", href: "/store/dashboard" },
|
||||||
{ text: "Agent dashboard", href: "/store/agent-dashboard" },
|
{ text: "Agent dashboard", href: "/store/agent-dashboard" },
|
||||||
{ text: "Integrations", href: "/store/integrations" },
|
{ text: "Integrations", href: "/store/integrations" },
|
||||||
|
{ text: "API Keys", href: "/store/api_keys" },
|
||||||
{ text: "Profile", href: "/store/profile" },
|
{ text: "Profile", href: "/store/profile" },
|
||||||
{ text: "Settings", href: "/store/settings" },
|
{ text: "Settings", href: "/store/settings" },
|
||||||
],
|
],
|
||||||
|
@ -17,7 +18,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen w-screen max-w-[1360px] flex-col lg:flex-row">
|
<div className="flex min-h-screen w-screen max-w-[1360px] flex-col lg:flex-row">
|
||||||
<Sidebar linkGroups={sidebarLinkGroups} />
|
<Sidebar linkGroups={sidebarLinkGroups} />
|
||||||
<div className="pl-4">{children}</div>
|
<div className="flex-1 pl-4">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
import { Menu } from "lucide-react";
|
import { KeyIcon, Menu } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
IconDashboardLayout,
|
IconDashboardLayout,
|
||||||
IconIntegrations,
|
IconIntegrations,
|
||||||
|
@ -58,6 +58,15 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
Integrations
|
Integrations
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/store/api_keys"
|
||||||
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
<KeyIcon className="h-6 w-6" />
|
||||||
|
<div className="p-ui-medium text-base font-medium leading-normal">
|
||||||
|
API Keys
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/profile"
|
href="/store/profile"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
|
@ -102,6 +111,15 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
Integrations
|
Integrations
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/store/api_keys"
|
||||||
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
<KeyIcon className="h-6 w-6" strokeWidth={1} />
|
||||||
|
<div className="p-ui-medium text-base font-medium leading-normal">
|
||||||
|
API Keys
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/profile"
|
href="/store/profile"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
|
|
|
@ -0,0 +1,296 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import { APIKey, APIKeyPermission } from "@/lib/autogpt-server-api/types";
|
||||||
|
|
||||||
|
import { LuCopy } from "react-icons/lu";
|
||||||
|
import { Loader2, MoreVertical } from "lucide-react";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
export function APIKeysSection() {
|
||||||
|
const [apiKeys, setApiKeys] = useState<APIKey[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||||
|
const [isKeyDialogOpen, setIsKeyDialogOpen] = useState(false);
|
||||||
|
const [newKeyName, setNewKeyName] = useState("");
|
||||||
|
const [newKeyDescription, setNewKeyDescription] = useState("");
|
||||||
|
const [newApiKey, setNewApiKey] = useState("");
|
||||||
|
const [selectedPermissions, setSelectedPermissions] = useState<
|
||||||
|
APIKeyPermission[]
|
||||||
|
>([]);
|
||||||
|
const { toast } = useToast();
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadAPIKeys();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadAPIKeys = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const keys = await api.listAPIKeys();
|
||||||
|
setApiKeys(keys.filter((key) => key.status === "ACTIVE"));
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateKey = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.createAPIKey(
|
||||||
|
newKeyName,
|
||||||
|
selectedPermissions,
|
||||||
|
newKeyDescription,
|
||||||
|
);
|
||||||
|
|
||||||
|
setNewApiKey(response.plain_text_key);
|
||||||
|
setIsCreateOpen(false);
|
||||||
|
setIsKeyDialogOpen(true);
|
||||||
|
loadAPIKeys();
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Failed to create AutoGPT Platform API key",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyKey = () => {
|
||||||
|
navigator.clipboard.writeText(newApiKey);
|
||||||
|
toast({
|
||||||
|
title: "Copied",
|
||||||
|
description: "AutoGPT Platform API key copied to clipboard",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRevokeKey = async (keyId: string) => {
|
||||||
|
try {
|
||||||
|
await api.revokeAPIKey(keyId);
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "AutoGPT Platform API key revoked successfully",
|
||||||
|
});
|
||||||
|
loadAPIKeys();
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Failed to revoke AutoGPT Platform API key",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>AutoGPT Platform API Keys</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage your AutoGPT Platform API keys for programmatic access
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="mb-4 flex justify-end">
|
||||||
|
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>Create Key</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New API Key</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Create a new AutoGPT Platform API key
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="name">Name</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
value={newKeyName}
|
||||||
|
onChange={(e) => setNewKeyName(e.target.value)}
|
||||||
|
placeholder="My AutoGPT Platform API Key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="description">Description (Optional)</Label>
|
||||||
|
<Input
|
||||||
|
id="description"
|
||||||
|
value={newKeyDescription}
|
||||||
|
onChange={(e) => setNewKeyDescription(e.target.value)}
|
||||||
|
placeholder="Used for..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label>Permissions</Label>
|
||||||
|
{Object.values(APIKeyPermission).map((permission) => (
|
||||||
|
<div
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
key={permission}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={permission}
|
||||||
|
checked={selectedPermissions.includes(permission)}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setSelectedPermissions(
|
||||||
|
checked
|
||||||
|
? [...selectedPermissions, permission]
|
||||||
|
: selectedPermissions.filter(
|
||||||
|
(p) => p !== permission,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={permission}>{permission}</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsCreateOpen(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleCreateKey}>Create</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog open={isKeyDialogOpen} onOpenChange={setIsKeyDialogOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>AutoGPT Platform API Key Created</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Please copy your AutoGPT API key now. You won't be able
|
||||||
|
to see it again!
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<code className="flex-1 rounded-md bg-secondary p-2 text-sm">
|
||||||
|
{newApiKey}
|
||||||
|
</code>
|
||||||
|
<Button size="icon" variant="outline" onClick={handleCopyKey}>
|
||||||
|
<LuCopy className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={() => setIsKeyDialogOpen(false)}>Close</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex justify-center p-4">
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
apiKeys.length > 0 && (
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>API Key</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Created</TableHead>
|
||||||
|
<TableHead>Last Used</TableHead>
|
||||||
|
<TableHead></TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{apiKeys.map((key) => (
|
||||||
|
<TableRow key={key.id}>
|
||||||
|
<TableCell>{key.name}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="rounded-md border p-1 px-2 text-xs">
|
||||||
|
{`${key.prefix}******************${key.postfix}`}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge
|
||||||
|
variant={
|
||||||
|
key.status === "ACTIVE" ? "default" : "destructive"
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
key.status === "ACTIVE"
|
||||||
|
? "border-green-600 bg-green-100 text-green-800"
|
||||||
|
: "border-red-600 bg-red-100 text-red-800"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{key.status}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(key.created_at).toLocaleDateString()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{key.last_used_at
|
||||||
|
? new Date(key.last_used_at).toLocaleDateString()
|
||||||
|
: "Never"}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="sm">
|
||||||
|
<MoreVertical className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-destructive"
|
||||||
|
onClick={() => handleRevokeKey(key.id)}
|
||||||
|
>
|
||||||
|
Revoke
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -29,6 +29,9 @@ import {
|
||||||
StoreReview,
|
StoreReview,
|
||||||
ScheduleCreatable,
|
ScheduleCreatable,
|
||||||
Schedule,
|
Schedule,
|
||||||
|
APIKeyPermission,
|
||||||
|
CreateAPIKeyResponse,
|
||||||
|
APIKey,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { createBrowserClient } from "@supabase/ssr";
|
import { createBrowserClient } from "@supabase/ssr";
|
||||||
import getServerSupabase from "../supabase/getServerSupabase";
|
import getServerSupabase from "../supabase/getServerSupabase";
|
||||||
|
@ -221,6 +224,36 @@ export default class BackendAPI {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API Key related requests
|
||||||
|
async createAPIKey(
|
||||||
|
name: string,
|
||||||
|
permissions: APIKeyPermission[],
|
||||||
|
description?: string,
|
||||||
|
): Promise<CreateAPIKeyResponse> {
|
||||||
|
return this._request("POST", "/api-keys", {
|
||||||
|
name,
|
||||||
|
permissions,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async listAPIKeys(): Promise<APIKey[]> {
|
||||||
|
return this._get("/api-keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
async revokeAPIKey(keyId: string): Promise<APIKey> {
|
||||||
|
return this._request("DELETE", `/api-keys/${keyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAPIKeyPermissions(
|
||||||
|
keyId: string,
|
||||||
|
permissions: APIKeyPermission[],
|
||||||
|
): Promise<APIKey> {
|
||||||
|
return this._request("PUT", `/api-keys/${keyId}/permissions`, {
|
||||||
|
permissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns `true` if a ping event was received, `false` if provider doesn't support pinging but the webhook exists.
|
* @returns `true` if a ping event was received, `false` if provider doesn't support pinging but the webhook exists.
|
||||||
* @throws `Error` if the webhook does not exist.
|
* @throws `Error` if the webhook does not exist.
|
||||||
|
|
|
@ -513,3 +513,36 @@ export type StoreReviewCreate = {
|
||||||
score: number;
|
score: number;
|
||||||
comments?: string;
|
comments?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// API Key Types
|
||||||
|
|
||||||
|
export enum APIKeyPermission {
|
||||||
|
EXECUTE_GRAPH = "EXECUTE_GRAPH",
|
||||||
|
READ_GRAPH = "READ_GRAPH",
|
||||||
|
EXECUTE_BLOCK = "EXECUTE_BLOCK",
|
||||||
|
READ_BLOCK = "READ_BLOCK",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum APIKeyStatus {
|
||||||
|
ACTIVE = "ACTIVE",
|
||||||
|
REVOKED = "REVOKED",
|
||||||
|
SUSPENDED = "SUSPENDED",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIKey {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
prefix: string;
|
||||||
|
postfix: string;
|
||||||
|
status: APIKeyStatus;
|
||||||
|
permissions: APIKeyPermission[];
|
||||||
|
created_at: string;
|
||||||
|
last_used_at?: string;
|
||||||
|
revoked_at?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateAPIKeyResponse {
|
||||||
|
api_key: APIKey;
|
||||||
|
plain_text_key: string;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue