page.tsx449 lines · main
1import { DocsShell } from '../../components/shell';
2
3export const metadata = { title: 'http api' };
4
5interface Endpoint {
6 method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
7 path: string;
8 summary: string;
9}
10
11interface Section {
12 title: string;
13 intro: string;
14 endpoints: Endpoint[];
15}
16
17const SECTIONS: readonly Section[] = [
18 {
19 title: 'invoke',
20 intro:
21 'Call a deployed function. Auth: project API key (brk_…) in Authorization: Bearer header.',
22 endpoints: [
23 {
24 method: 'POST',
25 path: '/v1/projects/:id/invoke',
26 summary:
27 'Body: { function: string, args?: unknown }. Returns { result, durationMs } or { error }. Counts against your project\'s monthly invocation cap.',
28 },
29 ],
30 },
31 {
32 title: 'realtime',
33 intro:
34 'Subscribe to reactive queries over WebSocket. SDK clients (@briven/react / svelte / vue) wrap this.',
35 endpoints: [
36 {
37 method: 'GET',
38 path: '/v1/projects/:id/realtime',
39 summary:
40 'WSS upgrade. Send { type: "subscribe", function, args }, receive frames as the underlying rows change. Auth: project API key.',
41 },
42 ],
43 },
44 {
45 title: 'projects',
46 intro: 'Project CRUD. Auth: dashboard session cookie.',
47 endpoints: [
48 { method: 'GET', path: '/v1/projects', summary: 'List every project the caller can see.' },
49 {
50 method: 'POST',
51 path: '/v1/projects',
52 summary:
53 'Body: { name, slug?, region?, orgId? }. Creates a fresh schema + runtime under the named org (defaults to your personal org).',
54 },
55 { method: 'GET', path: '/v1/projects/:id', summary: 'Project details.' },
56 {
57 method: 'PATCH',
58 path: '/v1/projects/:id',
59 summary: 'Body: { name?, slug? }. Rename a project.',
60 },
61 {
62 method: 'POST',
63 path: '/v1/projects/:id/move',
64 summary:
65 'Body: { orgId }. Re-parent to a different org (you must be a member of both).',
66 },
67 {
68 method: 'DELETE',
69 path: '/v1/projects/:id',
70 summary:
71 'Soft-delete. The schema and data are retained for 30 days before hard deletion.',
72 },
73 ],
74 },
75 {
76 title: 'deployments',
77 intro: 'Bundle uploads + history. Auth: project API key (POST) or session (GET).',
78 endpoints: [
79 {
80 method: 'POST',
81 path: '/v1/projects/:id/deployments',
82 summary:
83 'Upload a schema + functions bundle. Body: multipart form with bundle.tar.gz. Server diffs against the live schema, generates a migration, applies transactionally.',
84 },
85 {
86 method: 'GET',
87 path: '/v1/projects/:id/deployments',
88 summary: 'List recent deployments with their status + schema diff summary.',
89 },
90 {
91 method: 'GET',
92 path: '/v1/projects/:id/deployments/:deploymentId',
93 summary: 'Deployment detail including error code/message if it failed.',
94 },
95 {
96 method: 'POST',
97 path: '/v1/projects/:id/deployments/:deploymentId/cancel',
98 summary: 'Cancel a pending or running deployment.',
99 },
100 ],
101 },
102 {
103 title: 'studio',
104 intro:
105 'Dashboard data browser + DDL surface. Auth: dashboard session, admin role on the project.',
106 endpoints: [
107 { method: 'GET', path: '/v1/projects/:id/studio/tables', summary: 'List user tables.' },
108 {
109 method: 'POST',
110 path: '/v1/projects/:id/studio/tables',
111 summary:
112 'Body: { tableName, columns: [{ name, type, notNull?, primaryKey?, defaultExpr?, references? }] }. CREATE TABLE.',
113 },
114 {
115 method: 'PATCH',
116 path: '/v1/projects/:id/studio/tables/:table',
117 summary: 'Body: { newName }. Rename a table.',
118 },
119 {
120 method: 'POST',
121 path: '/v1/projects/:id/studio/tables/:table/truncate',
122 summary: 'Body: { cascade? }. TRUNCATE TABLE … RESTART IDENTITY.',
123 },
124 {
125 method: 'DELETE',
126 path: '/v1/projects/:id/studio/tables/:table',
127 summary: 'DROP TABLE.',
128 },
129 {
130 method: 'GET',
131 path: '/v1/projects/:id/studio/tables/:table/columns',
132 summary: 'Per-column metadata with PK + FK target.',
133 },
134 {
135 method: 'POST',
136 path: '/v1/projects/:id/studio/tables/:table/columns',
137 summary: 'Body: { column: { name, type, notNull?, defaultExpr?, references? } }. ADD COLUMN.',
138 },
139 {
140 method: 'PATCH',
141 path: '/v1/projects/:id/studio/tables/:table/columns/:column',
142 summary:
143 'Two-mode: { newName } to rename, or { notNull?, defaultExpr? } to alter nullability / default.',
144 },
145 {
146 method: 'DELETE',
147 path: '/v1/projects/:id/studio/tables/:table/columns/:column',
148 summary: 'DROP COLUMN.',
149 },
150 {
151 method: 'GET',
152 path: '/v1/projects/:id/studio/tables/:table/indexes',
153 summary: 'List indexes on a table.',
154 },
155 {
156 method: 'POST',
157 path: '/v1/projects/:id/studio/tables/:table/indexes',
158 summary: 'Body: { columns: string[], unique?, name? }. CREATE INDEX.',
159 },
160 {
161 method: 'DELETE',
162 path: '/v1/projects/:id/studio/tables/:table/indexes/:name',
163 summary: 'DROP INDEX (refuses the primary-key index).',
164 },
165 {
166 method: 'GET',
167 path: '/v1/projects/:id/studio/tables/:table/rows',
168 summary:
169 'Paginated rows. Query: limit, offset, orderBy + dir, and per-column <col>__eq=value filters.',
170 },
171 {
172 method: 'POST',
173 path: '/v1/projects/:id/studio/tables/:table/rows',
174 summary: 'Body: { values: { col: value, … } }. Insert a row.',
175 },
176 {
177 method: 'PATCH',
178 path: '/v1/projects/:id/studio/tables/:table/rows',
179 summary:
180 'Body: { primaryKeyColumn, primaryKeyValue, column, value }. Update a single cell.',
181 },
182 {
183 method: 'DELETE',
184 path: '/v1/projects/:id/studio/tables/:table/rows',
185 summary: 'Body: { primaryKeyColumn, primaryKeyValue }. Delete a row.',
186 },
187 {
188 method: 'POST',
189 path: '/v1/projects/:id/studio/query',
190 summary:
191 'Body: { sql }. Run arbitrary SQL scoped to the project owner role. 5s statement_timeout. Audit-logged.',
192 },
193 {
194 method: 'GET',
195 path: '/v1/projects/:id/studio/schema',
196 summary: 'One-shot read of every table + columns + FK edges.',
197 },
198 {
199 method: 'GET',
200 path: '/v1/projects/:id/studio/schema.ts',
201 summary:
202 'Generates the equivalent briven/schema.ts so a dashboard-built database can graduate to git + CLI.',
203 },
204 {
205 method: 'GET',
206 path: '/v1/projects/:id/studio/relationships',
207 summary: 'Every FK edge in the schema (used by the studio overview).',
208 },
209 ],
210 },
211 {
212 title: 'logs + stats',
213 intro: 'Function invocation history. Auth: dashboard session.',
214 endpoints: [
215 {
216 method: 'GET',
217 path: '/v1/projects/:id/function-logs',
218 summary: 'Query: function, status (ok|err), before (cursor), limit.',
219 },
220 {
221 method: 'GET',
222 path: '/v1/projects/:id/function-names',
223 summary: 'Distinct function names actually called in this project.',
224 },
225 {
226 method: 'GET',
227 path: '/v1/projects/:id/function-stats',
228 summary:
229 'Query: function (required), hours (default 24). Returns count + errCount + p50Ms + p99Ms.',
230 },
231 {
232 method: 'GET',
233 path: '/v1/projects/:id/hourly-invocations',
234 summary: '24-hour series of invocation counts per hour (used by the project sparkline).',
235 },
236 {
237 method: 'GET',
238 path: '/v1/projects/:id/logs/stream',
239 summary: 'SSE stream of new invocations as they arrive.',
240 },
241 ],
242 },
243 {
244 title: 'usage',
245 intro: 'Metering. Auth: project API key or session.',
246 endpoints: [
247 {
248 method: 'GET',
249 path: '/v1/projects/:id/usage',
250 summary: 'Current period: invocations.count + totalDurationMs, storage.bytes, limits.',
251 },
252 ],
253 },
254 {
255 title: 'api keys',
256 intro: 'Per-project deploy / invoke keys.',
257 endpoints: [
258 { method: 'GET', path: '/v1/projects/:id/api-keys', summary: 'List keys (hashes only).' },
259 {
260 method: 'POST',
261 path: '/v1/projects/:id/api-keys',
262 summary:
263 'Body: { name, scope }. Returns the plaintext key once — record it then; never retrievable again.',
264 },
265 {
266 method: 'PATCH',
267 path: '/v1/projects/:id/api-keys/:keyId',
268 summary: 'Rename a key.',
269 },
270 {
271 method: 'DELETE',
272 path: '/v1/projects/:id/api-keys/:keyId',
273 summary: 'Revoke a key.',
274 },
275 ],
276 },
277 {
278 title: 'project members + invitations',
279 intro: 'Per-project access control (separate from org membership).',
280 endpoints: [
281 {
282 method: 'GET',
283 path: '/v1/projects/:id/members',
284 summary: 'List members + roles.',
285 },
286 {
287 method: 'POST',
288 path: '/v1/projects/:id/invitations',
289 summary: 'Body: { email, role, callbackURL }. Sends an email with a one-time accept link.',
290 },
291 {
292 method: 'GET',
293 path: '/v1/me/invitations',
294 summary: 'Pending project invitations for the signed-in user.',
295 },
296 {
297 method: 'POST',
298 path: '/v1/me/invitations/accept',
299 summary: 'Body: { token }. Accept by token from the email link.',
300 },
301 ],
302 },
303 {
304 title: 'orgs (teams)',
305 intro: 'Multi-org workspace. Personal org is auto-created on signup.',
306 endpoints: [
307 { method: 'GET', path: '/v1/me/orgs', summary: 'Every org the caller belongs to.' },
308 {
309 method: 'POST',
310 path: '/v1/orgs',
311 summary:
312 'Body: { name, slug? }. Create a team org (paid tiers only — free tier caps at the personal org).',
313 },
314 {
315 method: 'PATCH',
316 path: '/v1/orgs/:id',
317 summary: 'Body: { name }. Rename a team org.',
318 },
319 {
320 method: 'DELETE',
321 path: '/v1/orgs/:id',
322 summary:
323 'Soft-delete a team org. Refuses while live projects still belong to it; move or delete them first.',
324 },
325 {
326 method: 'GET',
327 path: '/v1/orgs/:id/members',
328 summary: 'List org members with roles.',
329 },
330 {
331 method: 'PATCH',
332 path: '/v1/orgs/:id/members/:userId',
333 summary: 'Body: { role }. Change role. Refuses to demote the last owner.',
334 },
335 {
336 method: 'DELETE',
337 path: '/v1/orgs/:id/members/:userId',
338 summary: 'Remove a member.',
339 },
340 {
341 method: 'GET',
342 path: '/v1/orgs/:id/invitations',
343 summary: 'Pending org invitations.',
344 },
345 {
346 method: 'POST',
347 path: '/v1/orgs/:id/invitations',
348 summary: 'Body: { email, role, callbackURL }. Invite a collaborator.',
349 },
350 {
351 method: 'POST',
352 path: '/v1/org-invitations/accept',
353 summary: 'Body: { token }.',
354 },
355 ],
356 },
357 {
358 title: 'billing',
359 intro: 'Polar.sh-backed subscription management.',
360 endpoints: [
361 {
362 method: 'GET',
363 path: '/v1/billing/subscription',
364 summary: 'Current tier, status, period end, polar customer id.',
365 },
366 {
367 method: 'GET',
368 path: '/v1/billing/plans',
369 summary: 'Public plan + price list (matches docs.briven.tech/pricing).',
370 },
371 {
372 method: 'POST',
373 path: '/v1/billing/checkout',
374 summary:
375 'Body: { tier, successURL }. Returns { url } — hosted Polar checkout URL.',
376 },
377 {
378 method: 'POST',
379 path: '/v1/billing/portal',
380 summary: 'Returns { url } for the Polar customer portal (manage payment / cancel).',
381 },
382 {
383 method: 'POST',
384 path: '/v1/billing/webhook',
385 summary:
386 'Polar.sh webhook receiver. HMAC-validated. Server-side only — your app doesn\'t call this.',
387 },
388 ],
389 },
390];
391
392export default function ApiPage() {
393 return (
394 <DocsShell>
395 <h1 className="font-mono text-2xl tracking-tight">http api</h1>
396 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
397 every dashboard surface is built on these endpoints; you can build your own client
398 on top of them too. base url <code>https://api.briven.tech</code>. all responses are
399 JSON unless otherwise noted. auth is either a dashboard session cookie (Better Auth)
400 or a project API key in <code>Authorization: Bearer brk_…</code>; each section calls
401 out which.
402 </p>
403
404 <p className="mt-4 font-mono text-sm text-[var(--color-text-muted)]">
405 admin-only routes under <code>/v1/admin/*</code> aren&apos;t listed here — they need
406 platform-admin and are documented in the operator runbook.
407 </p>
408
409 {SECTIONS.map((sec) => (
410 <section key={sec.title} className="mt-10">
411 <h2 className="font-mono text-lg tracking-tight">{sec.title}</h2>
412 <p className="mt-1 font-mono text-sm text-[var(--color-text-muted)]">{sec.intro}</p>
413 <ul className="mt-4 flex flex-col gap-2">
414 {sec.endpoints.map((e, i) => (
415 <li
416 key={i}
417 className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-3 font-mono text-xs"
418 >
419 <p>
420 <span
421 className={`mr-2 inline-block w-12 rounded-sm px-1.5 py-0.5 text-center text-[10px] ${methodColour(e.method)}`}
422 >
423 {e.method}
424 </span>
425 <code className="text-[var(--color-text)]">{e.path}</code>
426 </p>
427 <p className="mt-1 text-[var(--color-text-muted)]">{e.summary}</p>
428 </li>
429 ))}
430 </ul>
431 </section>
432 ))}
433 </DocsShell>
434 );
435}
436
437function methodColour(m: Endpoint['method']): string {
438 switch (m) {
439 case 'GET':
440 return 'bg-[var(--color-primary-subtle)] text-[var(--color-primary)]';
441 case 'POST':
442 return 'bg-emerald-400/15 text-emerald-400';
443 case 'PATCH':
444 case 'PUT':
445 return 'bg-amber-400/15 text-amber-400';
446 case 'DELETE':
447 return 'bg-red-400/15 text-red-400';
448 }
449}