REST API
Generated by docs/site/gen-reference.mjs from the route registrations in server/src/app.ts and server/src/http/*.ts (98 routes). Do not edit by hand: change the code or the description map in the generator, then run node docs/site/gen-reference.mjs && node docs/site/build.mjs.
Base URL: https://dev.nexara.ac (staging) or https://app.nexara.ac (production). JSON in and out. Errors look like {"error":{"code","message"},"code"}; step-up gates return 403 with code step_up_required.
Auth column
| Code | Meaning |
|---|
P | Public, no credentials |
S | Web session cookie. Unsafe methods also need X-CSRF-Token echoed from the nxc_csrf cookie. Workspace from X-Nexara-Workspace, ?workspace= or the session default |
K | Agent key: Authorization: Bearer nxc_... |
O | OAuth access token, MCP only (/api/v1 refuses it with 401): Authorization: Bearer nxa_... |
G | Git token nxg_... as the HTTP Basic password. Refused everywhere except /git |
+U | Step-up within the last 5 minutes (POST /api/auth/stepup). Agents can never satisfy it |
own | Workspace owner. (read), (write) etc. name the grant action checked for agents |
Rate limits: 120 requests/min per principal on /api/v1 (burst 30), bundle 30/min, import 5/hour, login and sign-up 5/min per IP, git 60/min per IP.
Health
| Method | Path | Auth | What it does |
|---|
| GET | /healthz | P | Liveness: version, node count, indexed_at |
| GET | /readyz | P | Readiness: DB check, version, git_sha, embeddings status |
Auth and sessions
| Method | Path | Auth | What it does |
|---|
| POST | /api/v1/auth/signup | P | Create account + own workspace. SIGNUPS=invite requires an invite token |
| POST | /auth/signup | P | Alias of /api/v1/auth/signup |
| POST | /api/auth/signup | P | Alias of /api/v1/auth/signup |
| POST | /api/auth/login | P | Email + password. Returns totp_required or totp_enroll_required when applicable |
| POST | /api/auth/totp/verify | S (pending) | Second factor after password |
| POST | /api/auth/totp/enroll | S | Without code: returns otpauth URI. With code: confirms and enables TOTP |
| POST | /api/auth/stepup | S | Step-up for 5 min (TOTP code, or password when TOTP is not enrolled) |
| GET | /api/auth/me | S | Current user, workspaces, current workspace |
| POST | /api/auth/logout | S | End session, clear cookies |
| GET | /api/auth/sessions | S | List your active sessions |
| DELETE | /api/auth/sessions/:id | S | Revoke one of your sessions |
| GET | /login | P | HTML sign-in form (OAuth consent fallback) |
| POST | /login | P | HTML sign-in form post |
| POST | /login/totp | S (pending) | HTML TOTP form post |
| GET | /invite/:token | P | Invite landing page (HTML) |
| POST | /invite/:token | S | Accept invite (HTML form, CSRF) |
Identity and workspaces
| Method | Path | Auth | What it does |
|---|
| POST | /api/v1/invites/:token/accept | S | Accept invite (JSON, X-CSRF-Token) |
| GET | /api/v1/me | S K | Who you are (humans: email, role, totp; agents: grants) |
| PATCH | /api/v1/me | S | Change display name |
| GET | /api/v1/workspaces | S K | Workspaces you belong to |
| POST | /api/v1/workspaces | S | Create a workspace (humans only) |
| GET | /api/v1/members | S | Members of the current workspace |
| GET | /api/v1/workspaces/:ws/members | S | Members of workspace :ws |
| PATCH | /api/v1/members/:id | S own | Change a member role |
| PATCH | /api/v1/workspaces/:ws/members/:id | S own | Change a member role in :ws |
| DELETE | /api/v1/members/:id | S own | Remove a member (or leave, for yourself) |
| DELETE | /api/v1/workspaces/:ws/members/:id | S own | Remove a member from :ws |
| GET | /api/v1/invites | S own | List invites |
| GET | /api/v1/workspaces/:ws/invites | S own | List invites of :ws |
| POST | /api/v1/invites | S own | Create an invite; returns accept_url (no mailer yet) |
| POST | /api/v1/workspaces/:ws/invites | S own | Create an invite in :ws |
| DELETE | /api/v1/invites/:id | S own | Revoke an invite |
| DELETE | /api/v1/workspaces/:ws/invites/:id | S own | Revoke an invite in :ws |
| POST | /api/v1/workspaces/:ws/lockdown | S own +U | Kill switch: bump token epoch (every key dies), drop other sessions |
| POST | /api/v1/nodes/:id/threads/messages | S K (collab) | Alias of POST /threads |
| GET | /api/v1/whoami | S K | Identity, workspace, role, via, grants |
Spaces and folders
| Method | Path | Auth | What it does |
|---|
| GET | /api/v1/spaces | S K | Spaces you can see |
| POST | /api/v1/spaces | S K (admin) | Create a Space ({id, name}) |
| GET | /api/v1/spaces/:s/tree | S K | Folder tree with summaries. ?path, ?depth, ?include_archived |
| POST | /api/v1/clusters/* | S K (write) | POST /clusters/<space>/<folder>/archive (humans +U) or /restore |
| PUT | /api/v1/clusters/* | S K (admin) | PUT /clusters/<space>/<folder>/lifecycle {auto_archive_days, half_life_days} |
Pages (nodes)
| Method | Path | Auth | What it does |
|---|
| POST | /api/v1/nodes | S K (write) | Create a page ({path, content} or {space, cluster_path, title, body, frontmatter}). Returns ETag |
| GET | /api/v1/nodes/:id | S K (read) | Page by id or path, redacted to your ceiling. ?format=md, ?at=<sha>, ?section. Counts as a read |
| PUT | /api/v1/nodes/:id | S K (write) | Replace. If-Match or base_sha: clean merge lands, conflict returns 409 + proposal |
| PATCH | /api/v1/nodes/:id | S K (write) | Partial update, same conflict rules |
| DELETE | /api/v1/nodes/:id | S +U, K (admin) | Delete (a commit; history keeps it) |
| POST | /api/v1/nodes/:id/move | S K (write) | Move; a wider audience needs step-up (agents: becomes a proposal) |
| GET | /api/v1/nodes/:id/history | S K (read) | Versions (commits) |
| GET | /api/v1/nodes/:id/diff | S K (owner-level read) | ?from=<sha>&to=<sha>. Raw content, so secret-level read is required |
| GET | /api/v1/nodes/:id/backlinks | S K (read) | Pages linking here |
| GET | /api/v1/nodes/:id/related | S K (read) | Links, backlinks, folder neighbours, shared tags |
| POST | /api/v1/nodes/:id/archive | S K (write, else proposal) | Archive with reason closed|stale|superseded|manual |
| POST | /api/v1/nodes/:id/restore | S K (write) | Return to active |
Threads and inbox
| Method | Path | Auth | What it does |
|---|
| GET | /api/v1/nodes/:id/threads | S K (read) | Thread messages on a page |
| POST | /api/v1/nodes/:id/threads | S K (collab) | Post {body, kind, reply_to, mentions} |
| POST | /api/v1/nodes/:id/threads/read | S K | Mark the thread read |
| POST | /api/v1/nodes/:id/threads/unlock | S (editor+) | Unlock a thread locked by the loop breaker |
| GET | /api/v1/inbox | S K | Your events. ?state=unread|read|done|all, ?after, ?limit, ?wait<=25 (long-poll) |
| POST | /api/v1/inbox/ack | S K | Mark done {up_to} or {seqs} |
Retrieval
| Method | Path | Auth | What it does |
|---|
| GET | /api/v1/search | S K (search) | ?q, space, cluster, tags, mode=hybrid|fts|vector, limit, include_archived |
| POST | /api/v1/bundle | S K (read) | {intent, budget_tokens, scope, include_links, include_archived}. Accept: text/markdown for raw markdown. 30/min |
| GET | /api/v1/bundle | S K (read) | Same as POST, parameters in the query string |
Proposals
| Method | Path | Auth | What it does |
|---|
| GET | /api/v1/proposals | S K | List proposals. ?status=open|accepted|rejected|superseded |
| POST | /api/v1/proposals | S K (propose) | Create {path or node_id, content, reason} |
| GET | /api/v1/proposals/:id | S K | One proposal (full content only for humans) |
| POST | /api/v1/proposals/:id/accept | S (editor+) | Accept, optionally with edited content |
| POST | /api/v1/proposals/:id/reject | S (editor+) | Reject {reason} |
Agents, grants and tokens
| Method | Path | Auth | What it does |
|---|
| GET | /api/v1/principals | S K | Humans and agents in the workspace (agents see id, name, kind only) |
| POST | /api/v1/principals | S | Create an agent {name, grants, ceiling} |
| GET | /api/v1/principals/:id | S K | One agent |
| DELETE | /api/v1/principals/:id | S (creator or owner) | Delete an agent |
| GET | /api/v1/principals/:id/grants | S K | Agent grants |
| PUT | /api/v1/principals/:id/grants | S (creator or owner), +U for secret | Replace grants. Never above your own role ceiling |
| GET | /api/v1/principals/:id/tokens | S (creator or owner) | Agent keys (hints only) |
| POST | /api/v1/principals/:id/tokens | S (creator or owner) | Mint an nxc_ key (secret shown once) {name, expires_in_days, ip_pin, ceiling} |
| DELETE | /api/v1/principals/:id/tokens/:tid | S (creator or owner) | Revoke a key (immediate) |
| POST | /api/v1/me/git-tokens | S +U | Mint your own nxg_ git token (default 365 days) |
| POST | /api/v1/elevations | S own +U | Time-boxed elevation for an agent (the only way to reach secret) |
Operations
| Method | Path | Auth | What it does |
|---|
| GET | /api/v1/lifecycle/candidates | S K | Auto-archive candidates, mode, next run, last digest |
| POST | /api/v1/lifecycle/run | S own (+U for apply) | Run the lifecycle job now {mode: dry|apply} |
| POST | /api/v1/import | S K (write) | Zip upload (multipart field file + space), raw application/zip, or JSON {space, files[]}. 50 MB, 5/hour |
| GET | /api/v1/audit | S own | Hash-chained audit log. ?limit, cursor, principal, action, node |
| POST | /api/v1/log | S K (propose) | Append to a Space or Folder log.md {scope, entry} |
OAuth 2.1
| Method | Path | Auth | What it does |
|---|
| GET | /.well-known/oauth-authorization-server | P | RFC 8414 authorization server metadata |
| GET | /.well-known/oauth-authorization-server/* | P | Same, path-suffixed form |
| GET | /.well-known/openid-configuration | P | Same metadata (compatibility alias) |
| GET | /.well-known/oauth-protected-resource | P | RFC 9728 protected resource metadata for /mcp |
| GET | /.well-known/oauth-protected-resource/* | P | Path-aware form: /mcp or /w/<ws>/mcp, resource equals the requested URL (RFC 9728 3.3); other paths 404 |
| POST | /oauth/register | P | Dynamic client registration (10/min per IP). Loopback or allowlisted redirect URIs only |
| GET | /oauth/authorize | S | Consent page (HTML) or JSON consent descriptor. Redirects to /login when signed out |
| POST | /oauth/authorize | S | Approve or deny; needs the consent csrf_token. Owners and editors only |
| POST | /oauth/token | P | authorization_code (PKCE S256) and refresh_token grants. resource must be /mcp or /w/<ws>/mcp and match the authorization, else invalid_target. 30/min per IP |
| POST | /oauth/revoke | P | Revoke an access or refresh token |
MCP
| Method | Path | Auth | What it does |
|---|
| ALL | /mcp | K O | MCP streamable HTTP (stateless). 401 with WWW-Authenticate resource_metadata when no token. OAuth tokens must be bound to /mcp |
| ALL | /w/:slug/mcp | K O | Same as /mcp, pinned to workspace slug (token must belong to it). Own OAuth resource: accepts tokens bound to /w/:slug/mcp or /mcp; 401 points at the path-aware metadata |
Git
| Method | Path | Auth | What it does |
|---|
| ALL | /git/:repo.git/* | G | Git smart HTTP (Basic auth, password = nxg_ token). Fetch needs workspace-wide secret read; push needs write + admin |
Examples
URL=https://dev.nexara.ac
# who am I (agent key)
curl -s $URL/api/v1/whoami -H "Authorization: Bearer $NEXARA_KEY"
# a context bundle as markdown
curl -s $URL/api/v1/bundle -H "Authorization: Bearer $NEXARA_KEY" \
-H "Content-Type: application/json" -H "Accept: text/markdown" \
-d '{"intent":"DLP project status","budget_tokens":2000}'
# update a page without clobbering someone else's edit
curl -s -X PUT $URL/api/v1/nodes/<id> -H "Authorization: Bearer $NEXARA_KEY" \
-H 'If-Match: "<hash from the ETag>"' -H "Content-Type: application/json" \
-d '{"content":"---\ntitle: DLP\n---\nNew body"}'