API reference
The workspace API lets your dashboard or scripts manage a user's container. All endpoints are at app.blacknode.tech/api/ and require a Supabase JWT as a Bearer token.
Authentication
Every endpoint requires:
Authorization: Bearer <supabase_access_token>
Content-Type: application/json
Get the token from the Supabase JS client:
const { data: { session } } = await supabase.auth.getSession();
const token = session.access_token;
fetch('/api/workspace/me', {
headers: { Authorization: 'Bearer ' + token }
});
user_workspaces still applies. A user can only ever see their own row.
Base URL
https://app.blacknode.tech/api
Endpoints
/api/workspace/me
Returns the caller's active workspace plus a boolean for whether the user has completed brand-kit onboarding.
{
"ok": true,
"workspace": {
"id": "uuid",
"slug": "ondrej-x9c2",
"status": "running",
"plan": "pro",
"workspace_url": "https://ws-x9c2.proof.blacknode.tech",
"ram_mb": 2048,
"cpu_quota": 200000,
"provisioned_at": "2026-05-19T10:30:00Z",
"last_active_at": "2026-05-21T08:12:00Z"
},
"brand_kit_exists": true
}
/api/workspace/provision
Provisions a new container on Hetzner. Returns the one-shot password (never shown again). Plan must be one of starter / pro / ultra.
Request
{ "plan": "pro", "slug": "optional-32-char-slug" }
Response
{
"ok": true,
"workspace_id": "uuid",
"slug": "ondrej-x9c2",
"workspace_url": "https://ws-x9c2.proof.blacknode.tech",
"password": "k8Hf7XzqR2p"
}
/api/workspace/status
Live status of the caller's container. Same fields as /me but no brand-kit flag and slightly less metadata.
{
"ok": true,
"status": "provisioning" | "running" | "stopped" | "destroyed" | "error",
"uptime_seconds": 18342,
"ram_used_mb": 612,
"disk_used_gb": 4.2
}
/api/workspace/restart
Restarts the container in-place. Preserves /workspace volume. ~10s downtime.
// empty body
{ "ok": true, "status": "restarting" }
/api/workspace/destroy
Destroys the container and wipes the volume. Irreversible. We recommend exporting to R2 first via the dashboard danger zone.
{ "confirm": "destroy-my-workspace" }
// response:
{ "ok": true, "status": "destroyed" }
Error format
All non-2xx responses share this shape:
{ "error": "rate_limited", "detail": "5 requests in 60s, slow down" }
Status codes
200— success.401— missing or invalid Bearer token.403— token valid but user not allowed (e.g. another user's workspace).404— workspace not found.409— workspace already exists / busy.429— rate limit (max 5 provisioner calls / 60s).500— provisioner unreachable; safe to retry after 10s.