page.tsx212 lines · main
1import { DocsShell } from '../../components/shell';
2
3export const metadata = {
4 title: 'api keys',
5};
6
7interface Role {
8 name: string;
9 plain: string;
10 can: string;
11}
12
13const ROLES: readonly Role[] = [
14 {
15 name: 'viewer',
16 plain: 'read-only',
17 can: 'read data and run read-only functions. safe to ship in a browser bundle. cannot write, deploy, or change settings.',
18 },
19 {
20 name: 'developer',
21 plain: 'read + write',
22 can: 'everything viewer can, plus invoke write functions and deploy. this is the day-to-day key for your server / CI. keep it server-side.',
23 },
24 {
25 name: 'admin',
26 plain: 'full project control',
27 can: 'everything developer can, plus manage the project, its members, and its keys. use sparingly.',
28 },
29];
30
31export default function ApiKeysPage() {
32 return (
33 <DocsShell>
34 <h1 className="font-mono text-2xl tracking-tight">api keys</h1>
35 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
36 an API key (<code>brk_…</code>) is how your code proves which project it&apos;s allowed to
37 touch. every SDK and HTTP call sends one as <code>Authorization: Bearer brk_…</code>. keys
38 are per-project, so a key for one project can never reach another.
39 </p>
40
41 <h2 className="mt-12 font-mono text-lg">create one in the dashboard</h2>
42 <ol className="mt-4 flex flex-col gap-4 font-mono text-sm text-[var(--color-text-muted)]">
43 <Step n={1} title="open your project's api keys page">
44 sign in at{' '}
45 <a className="underline" href="https://briven.tech">
46 briven.tech
47 </a>
48 , open the project, and click <em>api keys</em>. you need the <code>admin</code> role on
49 the project to create or revoke keys.
50 </Step>
51 <Step n={2} title="click new key, name it, pick a role">
52 give it a name you&apos;ll recognise later (e.g. <code>prod-server</code> or{' '}
53 <code>vercel-preview</code>) and choose a role (see below). you can also set an optional
54 expiry in days — after which the key stops working on its own.
55 </Step>
56 <Step n={3} title="copy the key now — it is shown only once">
57 the full key (<code>brk_…</code>) is revealed a single time, right after you create it.
58 briven only stores a hashed fingerprint, so it can never show you the key again. copy it
59 straight into your secret manager or <code>.env</code> file before you close the dialog.
60 </Step>
61 </ol>
62
63 <h2 className="mt-12 font-mono text-lg">roles (what a key is allowed to do)</h2>
64 <p className="mt-3 font-mono text-sm text-[var(--color-text-muted)]">
65 each key is scoped to a role. pick the least powerful one that gets the job done — that way
66 a leaked key does the least damage. you can only issue a key at or below your own role, and{' '}
67 <code>owner</code> is never assignable to a key.
68 </p>
69 <div className="mt-4 flex flex-col gap-3">
70 {ROLES.map((r) => (
71 <div
72 key={r.name}
73 className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-3 font-mono text-xs text-[var(--color-text-muted)]"
74 >
75 <p>
76 <code className="text-[var(--color-text)]">{r.name}</code>{' '}
77 <span className="text-[var(--color-text-subtle)]">· {r.plain}</span>
78 </p>
79 <p className="mt-1">{r.can}</p>
80 </div>
81 ))}
82 </div>
83
84 <h2 className="mt-12 font-mono text-lg">one-time reveal</h2>
85 <p className="mt-3 font-mono text-sm text-[var(--color-text-muted)]">
86 when you create a key, the API returns the plaintext exactly once and never again — only a
87 hash is kept. if you lose a key, you can&apos;t recover it; create a fresh one and revoke
88 the old. there is no &ldquo;show key&rdquo; button anywhere by design.
89 </p>
90
91 <h2 className="mt-12 font-mono text-lg">keep keys secret</h2>
92 <ul className="mt-3 list-inside list-disc font-mono text-sm text-[var(--color-text-muted)]">
93 <li>
94 never commit a key to git. put it in <code>.env</code> (git-ignored) or a secret manager.
95 </li>
96 <li>
97 only a <code>viewer</code> (read-only) key may live in browser / client code — and even
98 then it&apos;s public, so scope it tightly. <code>developer</code> and <code>admin</code>{' '}
99 keys are server-side only.
100 </li>
101 <li>
102 rotate on a schedule and whenever someone leaves the team. give each
103 environment/service its own named key so you can revoke just one without downtime
104 elsewhere.
105 </li>
106 <li>
107 revoking a key takes effect immediately — the next request with it is rejected.
108 </li>
109 </ul>
110
111 <h2 className="mt-12 font-mono text-lg">managing keys over the API</h2>
112 <p className="mt-3 font-mono text-sm text-[var(--color-text-muted)]">
113 the dashboard is built on these endpoints (all require a dashboard session with the{' '}
114 <code>admin</code> role on the project):
115 </p>
116 <ul className="mt-4 flex flex-col gap-2 font-mono text-xs">
117 <ApiLine method="GET" path="/v1/projects/:id/api-keys" note="list keys (fingerprints only — never the plaintext)" />
118 <ApiLine
119 method="POST"
120 path="/v1/projects/:id/api-keys"
121 note="body { name, role?, expiresInDays? }. returns the plaintext once, then 201"
122 />
123 <ApiLine method="PATCH" path="/v1/projects/:id/api-keys/:keyId" note="rename a key" />
124 <ApiLine method="DELETE" path="/v1/projects/:id/api-keys/:keyId" note="revoke a key (effective immediately)" />
125 </ul>
126
127 <h2 className="mt-12 font-mono text-lg">what to read next</h2>
128 <ul className="mt-3 flex flex-col gap-2 font-mono text-sm">
129 <NextLink
130 href="/connect"
131 title="connect"
132 body="use your new key with the SDK, raw HTTP, or an MCP agent"
133 />
134 <NextLink
135 href="/sdks"
136 title="client sdks"
137 body="where the key goes in the react / svelte / vue clients"
138 />
139 <NextLink
140 href="/api"
141 title="http api"
142 body="the full endpoint reference, including auth details"
143 />
144 </ul>
145 </DocsShell>
146 );
147}
148
149function Step({ n, title, children }: { n: number; title: string; children: React.ReactNode }) {
150 return (
151 <li className="flex gap-4">
152 <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-[var(--color-primary)] text-[var(--color-text-inverse)]">
153 {n}
154 </span>
155 <div className="min-w-0 flex-1">
156 <p className="font-mono text-[var(--color-text)]">{title}</p>
157 <div className="mt-1 space-y-2">{children}</div>
158 </div>
159 </li>
160 );
161}
162
163function ApiLine({
164 method,
165 path,
166 note,
167}: {
168 method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
169 path: string;
170 note: string;
171}) {
172 return (
173 <li className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-3">
174 <p>
175 <span
176 className={`mr-2 inline-block w-12 rounded-sm px-1.5 py-0.5 text-center text-[10px] ${methodColour(method)}`}
177 >
178 {method}
179 </span>
180 <code className="text-[var(--color-text)]">{path}</code>
181 </p>
182 <p className="mt-1 text-[var(--color-text-muted)]">{note}</p>
183 </li>
184 );
185}
186
187function methodColour(m: 'GET' | 'POST' | 'PATCH' | 'DELETE'): string {
188 switch (m) {
189 case 'GET':
190 return 'bg-[var(--color-primary-subtle)] text-[var(--color-primary)]';
191 case 'POST':
192 return 'bg-emerald-400/15 text-emerald-400';
193 case 'PATCH':
194 return 'bg-amber-400/15 text-amber-400';
195 case 'DELETE':
196 return 'bg-red-400/15 text-red-400';
197 }
198}
199
200function NextLink({ href, title, body }: { href: string; title: string; body: string }) {
201 return (
202 <li>
203 <a
204 href={href}
205 className="block rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 transition hover:border-[var(--color-border)]"
206 >
207 <p className="font-mono text-[var(--color-text)]">{title}</p>
208 <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]">{body}</p>
209 </a>
210 </li>
211 );
212}