page.tsx212 lines · main
| 1 | import { DocsShell } from '../../components/shell'; |
| 2 | |
| 3 | export const metadata = { |
| 4 | title: 'api keys', |
| 5 | }; |
| 6 | |
| 7 | interface Role { |
| 8 | name: string; |
| 9 | plain: string; |
| 10 | can: string; |
| 11 | } |
| 12 | |
| 13 | const 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 | |
| 31 | export 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'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'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't recover it; create a fresh one and revoke |
| 88 | the old. there is no “show key” 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'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 | |
| 149 | function 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 | |
| 163 | function 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 | |
| 187 | function 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 | |
| 200 | function 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 | } |