Skip to content

HTTP API

The HTTP/REST surface is the language-agnostic way to call Bezial: authenticate with a Bearer token and call every verb over https. It is a thin transport over the same engine the library uses - never a reimplementation - built on Node’s own http module with no web framework, keeping the dependency tree at zero vulnerabilities. The only write path is still a file: there is deliberately no graph-write route, and none can be added.

The server reads its configuration from the environment:

Variable Default Purpose
BEZIAL_ROOT ./.bezial-data Object store + change logs root.
BEZIAL_PORT 8787 Listen port.
BEZIAL_HOST 127.0.0.1 Listen host.
BEZIAL_PERMISSIONS acl The ACL model is enforced by default; set allow-all to disable enforcement (trusted single-tenant).
BEZIAL_KEYS <root>/api-keys.json Where API-key records live (hashed, never plaintext).
BEZIAL_SIGNING_SECRET - If set, also accept signed-principal tokens.
BEZIAL_BOOTSTRAP_PRINCIPAL - If no keys exist, mint and print one key for this principal at startup.

Every request (except GET /v1/health) carries a bearer credential:

Authorization: Bearer bzk_<keyId>.<secret>

The server resolves the token to a principal once, at the edge, then authorizes every call with that principal exactly like the library path. An unresolvable token is 401 unauthorized.

Bezial-issued API keys have the form bzk_<keyId>.<secret> and are returned in plaintext exactly once at issue time; the store keeps only a salted SHA-256 hash, and every comparison is constant-time. An application with its own auth can instead pass a signed principal (a bzs_ token) that Bezial verifies with BEZIAL_SIGNING_SECRET - no Bezial keys required. See Identity for the seam.

All routes are under /v1/spaces/:space/…. The document path travels as a query or body field (never a path segment), so nested paths are unambiguous.

Method & path Fields Verb
GET /v1/health - (unauthenticated) health check
POST /v1/spaces/:space/notes body text add
PUT /v1/spaces/:space/document query path; body content write
GET /v1/spaces/:space/document query path, version? read / readVersion
GET /v1/spaces/:space/documents query prefix? list
GET /v1/spaces/:space/document/history query path history
POST /v1/spaces/:space/document/restore body path, version restore (version)
POST /v1/spaces/:space/document/grant body path, granteeId grant
POST /v1/spaces/:space/document/revoke body path, granteeId revoke
PUT /v1/spaces/:space/document/visibility body path, visibility (private|space) setVisibility
GET /v1/spaces/:space/document/acl query path acl
GET /v1/spaces/:space/recall query q, asOf?, scope?, synthesize?, limit?, includeArchived? recall
GET /v1/spaces/:space/views/file-map query asOf?, scope? fileMap
GET /v1/spaces/:space/views/fact-graph query asOf?, scope? factGraph
GET /v1/spaces/:space/audit query path?, principalId?, since? audit
POST /v1/spaces/:space/facts/pin body factId pin
POST /v1/spaces/:space/facts/unpin body factId unpin
POST /v1/spaces/:space/facts/forget body factId, reason forget
POST /v1/spaces/:space/facts/restore body factId restoreFact

Every call is permission-enforced with the resolved principal: list is filtered, audit is filtered, and recall and both views are filtered per fact. Zero cross-space and cross-author leak holds over HTTP exactly as in-process.

Terminal window
# Add a note.
curl -X POST http://localhost:8787/v1/spaces/team/notes \
-H "authorization: Bearer bzk_..." \
-H "content-type: application/json" \
-d '{"text":"Alice uses Obsidian."}'
# Recall, with a synthesized answer.
curl "http://localhost:8787/v1/spaces/team/recall?q=What+does+Alice+use%3F&synthesize=true" \
-H "authorization: Bearer bzk_..."

Errors are always the same shape, with a matching HTTP status:

{ "error": { "code": "permission_denied", "message": "..." } }
Status Code
400 bad_request
401 unauthorized
403 permission_denied
404 not_found (unknown document, fact, or route)
405 method_not_allowed
413 payload_too_large
500 internal (message is not exposed)

Prefer a typed client? The TypeScript and Python SDKs both ship a BezialHttpClient that speaks this API and returns the same shapes as the in-process engine.