Skip to content
BlackNode App
Sign in Start workspace

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 }
});
RLS. The token is forwarded to PostgREST, so row-level security on user_workspaces still applies. A user can only ever see their own row.

Base URL

https://app.blacknode.tech/api

Endpoints

GET /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
}
POST /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"
}
GET /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
}
POST /api/workspace/restart

Restarts the container in-place. Preserves /workspace volume. ~10s downtime.

// empty body
{ "ok": true, "status": "restarting" }
POST /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.
Last updated 2026-05-21 Edit on GitHub soon