import { FormEvent, useCallback, useEffect, useState } from 'react'; import Layout from '../../components/Layout'; import { api } from '../../lib/api'; import { useRequireAuth } from '../../lib/auth-context'; interface ApiKeyRow { id: string; name: string; keyPrefix: string; scopes: string[]; expiresAt: string | null; lastUsedAt: string | null; revokedAt: string | null; createdAt: string; } const ALL_SCOPES = ['jobs:read', 'jobs:write', 'points:read', 'points:write', 'devices:read']; export default function ApiKeysPage() { const { user, activeOrg, loading } = useRequireAuth(); const orgId = activeOrg?.org.id ?? null; const isAdmin = activeOrg?.role === 'ORG_ADMIN'; const [keys, setKeys] = useState([]); const [name, setName] = useState(''); const [scopes, setScopes] = useState(['jobs:read', 'points:read']); const [createdKey, setCreatedKey] = useState(null); const [error, setError] = useState(null); const reload = useCallback(() => { if (!orgId || !isAdmin) { return; } api .get(`/api/orgs/${orgId}/api-keys`) .then((rows) => { setKeys(rows); setError(null); }) .catch((err) => setError(err.message)); }, [orgId, isAdmin]); useEffect(reload, [reload]); const toggleScope = (scope: string) => setScopes((s) => (s.includes(scope) ? s.filter((x) => x !== scope) : [...s, scope])); const createKey = async (e: FormEvent) => { e.preventDefault(); try { const created = await api.post(`/api/orgs/${orgId}/api-keys`, { name, scopes }); setCreatedKey(created.key); setName(''); reload(); } catch (err: any) { setError(err.message); } }; const revoke = async (keyId: string) => { try { await api.delete(`/api/orgs/${orgId}/api-keys/${keyId}`); reload(); } catch (err: any) { setError(err.message); } }; if (loading || !user) { return null; } if (!isAdmin) { return (

API Keys

Only organization admins can manage API keys.

); } return (

API Keys

Send keys in the X-API-Key header. Access is limited to this organization and the selected scopes.

{error &&

{error}

} {createdKey && (
Copy this key now — it will not be shown again:
{createdKey}
)}
setName(e.target.value)} required style={{ padding: '0.4rem', width: 220 }} /> {ALL_SCOPES.map((scope) => ( ))}
{keys.map((k) => ( ))}
Name Prefix Scopes Last used Status
{k.name} {k.keyPrefix}… {k.scopes.join(', ')} {k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleString() : 'never'} {k.revokedAt ? 'revoked' : 'active'} {!k.revokedAt && }
); }