API Reference

Hive exposes a consistent REST API auto-generated from ModelDefinition metadata. All endpoints return JSON. Authentication uses short-lived Bearer access tokens with rotating refresh tokens. Every model endpoint passes through a validator pipeline and trigger pipeline before touching the database.

Base path and transport

http://<host>:<port>/api/v1/

Default: http://localhost:9000. The web frontend is served at /web/. HTTPS is recommended for production (configure a reverse proxy — nginx or Caddy — in front of Hive).

Authentication — Bearer tokens

All protected endpoints require a Bearer access token:

Authorization: Bearer <access_token>

Access tokens are short-lived (default: 15 minutes, configurable as access_token_expires_in in minutes). Use the refresh token endpoint to obtain a new access token without re-authenticating. Refresh tokens rotate: each use issues a new refresh token (old one invalidated). Default refresh token lifetime: 30 days (refresh_token_expires_in=43200 minutes).

Token lifecycle (frontend implementation)

The frontend's apiFetch() function in frontend/api.js handles token lifecycle transparently:

// Proactive refresh: if access token expires in < 60 seconds, refresh first
// On 401 response: attempt token refresh once, then retry original request
// On refresh failure: redirect user to login screen

// Token storage: localStorage
// Keys: "access_token", "refresh_token", "token_expiry" (Unix timestamp)
Token typeDefault lifetimeStorageRotation
Access token15 minuteslocalStorage (frontend)Issued on login + every refresh call
Refresh token30 dayslocalStorage (frontend)Rotates: each use invalidates old token and issues new one
Rotation threshold7 days before expiryControlled by refresh_token_rotation_threshold_in (minutes)

Authentication Endpoints

All authentication endpoints live under /api/v1/auth/ and are handled by AuthEndpointsGenerator in hive-http. The implementation in frontend/auth.js wraps these endpoints.

MethodPathDescriptionAuth Required
POST /api/v1/auth/login Authenticate with username + password. Returns access token, refresh token, and expiry. No
POST /api/v1/auth/logout Revoke the current session (invalidates the refresh token). Access token remains valid until natural expiry. Access token
POST /api/v1/auth/refresh_token Exchange a valid refresh token for a new access token + new refresh token. Old refresh token is invalidated. Refresh token in body
POST /api/v1/auth/register Register a new user account. Behaviour depends on registration_mode config: Free / RequiresAdminApproval / AdminAddsUsers. No (or Admin depending on mode)
POST /api/v1/auth/change_password Change the authenticated user's password. Requires old password for verification. Access token

Login — request and response

POST /api/v1/auth/login — Request:

Content-Type: application/json

{
  "username": "admin",
  "password": "your-secure-password"
}

Response 200 OK:

{
  "access_token":  "eyJhbGciOiJIUzI1NiIsIn...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsIn...",
  "expires_in":    900,
  "token_type":    "Bearer",
  "user": {
    "id":       1,
    "username": "admin",
    "role":     5
  }
}

expires_in is in seconds. role matches UserRole enum: 0=Guest 1=Reader 2=Editor 3=Reviewer 4=Admin 5=SuperAdmin 100=System.

Refresh token — request and response

POST /api/v1/auth/refresh_token — Request:

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsIn..."
}

Response 200 OK:

{
  "access_token":  "eyJhbGciOiJIUzI1NiIsIn...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsIn...",
  "expires_in":    900
}

Registration modes

ModeValueBehaviour
Free0Anyone can register. New accounts receive default_user_role (default: Reader) and status Active.
RequiresAdminApproval1Anyone can submit a registration request. Account gets status Pending. Admin must activate it.
AdminAddsUsers2Self-registration is disabled. Only Admin or SuperAdmin can create user accounts via SuperAdmin API.

Error responses

// 401 Unauthorized — wrong credentials or expired token
{ "error": "Invalid credentials" }

// 403 Forbidden — authenticated but not permitted
{ "error": "Forbidden", "details": "Insufficient role" }

// 409 Conflict — username already taken
{ "error": "Username already exists" }

Model Definition Endpoint

The single most important endpoint in Hive. The frontend reads this once at startup (cached in localStorage for 3 hours) and derives all navigation, CRUD forms, list columns, and FK dropdowns from it.

Handled by ModelDefinitionEndpointsGenerator. Returns the full ModelDefinition registry — one object per registered model across all loaded plugins.

MethodPathAuthDescription
GET /api/v1/model_definition Optional (affects visible models) Returns metadata for all registered models. Structure drives the entire frontend.

Full response structure (one model entry)

[
  {
    "name":          "note",
    "plugin":        "slip_box",
    "group":         "Slipbox",
    "title_column":  "title",
    "operations":    ["create", "read", "update", "delete", "list"],
    "cache_enabled": true,
    "readonly":      false,
    "columns": [
      {
        "name":        "id",
        "flags":       2056,        // INTEGER | AUTO | HIDDEN bitmask
        "type":        "INTEGER",
        "primary_key": true,
        "hidden":      true,
        "auto":        true,
        "mandatory":   false,
        "mutable":     false
      },
      {
        "name":        "title",
        "flags":       257,         // TEXT | MANDATORY bitmask
        "type":        "TEXT",
        "hidden":      false,
        "mandatory":   true,
        "mutable":     true,
        "readonly":    false
      },
      {
        "name":        "content",
        "flags":       514,         // TEXTAREA | MUTABLE
        "type":        "TEXTAREA",
        "hidden":      false,
        "mandatory":   false,
        "mutable":     true
      },
      {
        "name":        "parent_id",
        "flags":       1028,        // INTEGER | FOREIGN_KEY
        "type":        "INTEGER",
        "foreign_key_model": "note",
        "hidden":      false,
        "mandatory":   false,
        "mutable":     true
      }
    ],
    "custom_list_actions":   [],
    "custom_create_actions": [],
    "custom_read_actions":   []
  }
  // ... more models
]

ColumnDefinitionFlag bit values

Flag nameBit valueMeaning in API response
MANDATORY1 (1<<0)Field is required on create
UNIQUE2 (1<<1)Database UNIQUE constraint
FOREIGN_KEY4 (1<<2)References another model — frontend renders as <select>
AUTO8 (1<<3)Auto-generated (PK, timestamp) — excluded from create/update body
HIDDEN16 (1<<4)Not shown in any frontend view
READONLY32 (1<<5)Shown in read view, never in edit form
MUTABLE64 (1<<6)Editable in update form
INTERNAL128 (1<<7)System-only field, excluded from API responses
TEXT256 (1<<8)Single-line text input
TEXTAREA512 (1<<9)Multi-line textarea (Markdown in Slipbox/Dictionary)
INTEGER1024 (1<<10)Integer input
REAL2048 (1<<11)Floating-point input
BLOB4096 (1<<12)Binary data
BOOL8192 (1<<13)Checkbox / boolean
DATETIME16384 (1<<14)Datetime input (ISO 8601 string in API)

Auto-Generated Model Endpoints

For every registered model with enabled operations, ModelEndpointGenerator generates these routes at startup. The set of enabled operations is controlled per model via ModelDefinition::set_rest_operations() or set_all_rest_operations().

MethodPathOperation (Crudl)Request bodyDescription
GET /api/v1/<model> List (5) Paginated list of all records. Supports full QueryParams query string (see below).
GET /api/v1/<model>/:id Read (2) Fetch a single record by primary key. Runs can_read() validators.
POST /api/v1/<model> Create (1) JSON object (model fields) Create a new record. Runs can_create() validators → Before triggers → repository.create() → After triggers.
PUT /api/v1/<model>/:id Update (3) JSON object (changed fields) Update an existing record. Runs can_update() validators → Before triggers → repository.update() → After triggers.
DELETE /api/v1/<model>/:id Delete (4) Delete record by ID. Runs can_delete() validators → Before triggers → repository.remove() → After triggers (history written here).

Create request example

POST /api/v1/note — Request:

Content-Type: application/json
Authorization: Bearer <access_token>

{
  "title":     "My first note",
  "content":   "# Hello\n\nThis is **Markdown**.",
  "parent_id": null,
  "map_id":    3
}

Fields with AUTO flag (id, created_at, updated_at) must be omitted — they are set by the server.

Response 201 Created:

{
  "id":         42,
  "title":      "My first note",
  "content":    "# Hello\n\nThis is **Markdown**.",
  "parent_id":  null,
  "map_id":     3,
  "path":       "/maps/3/42",
  "note_order": 0,
  "created_at": "2026-06-26T10:30:00Z",
  "updated_at": null
}

path is set by a Before trigger — not sent in the request body.

List response format

// GET /api/v1/note?page=1&page_size=20

{
  "items":       [ ... ],       // array of entity objects
  "total":       142,           // total records matching query
  "total_pages": 8,             // ceil(total / page_size)
  "page":        1,
  "page_size":   20
}

Error response format

// 400 Bad Request — missing mandatory field
{
  "error":   "Validation failed",
  "details": "Field 'title' is mandatory and was not provided."
}

// 403 Forbidden — validator rejection
{
  "error":   "Forbidden",
  "details": "You do not have permission to modify this record."
}

// 404 Not Found
{
  "error":   "Not found",
  "details": "No record with id=999 in model 'note'."
}

// 405 Method Not Allowed — operation not enabled for this model
{
  "error":   "Method not allowed",
  "details": "Operation 'delete' is not enabled for model 'history'."
}

QueryParams — List Endpoint Parameters

The frontend's QueryParams class (in frontend/api.js) constructs the query string for list requests. All parameters are optional.

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
page_sizeinteger20Records per page (max configurable, typically 100)
sortstringidColumn name to sort by
orderasc | descascSort direction
qstringFulltext search query — if present, may trigger an InsteadOf search trigger (model-dependent)
filter[field]stringEquality filter on a column, e.g. filter[map_id]=3
idscomma-separated integersFetch only specific IDs — used by list_in_ids() for FK label resolution

QueryParams usage in frontend API

// From frontend/api.js — QueryParams class
class QueryParams {
    constructor() {
        this.page      = 1;
        this.page_size = 20;
        this.sort      = 'id';
        this.order     = 'asc';
        this.q         = null;      // search query
        this.filters   = {};        // { field: value }
    }

    toQueryString() {
        // Serialises to URL query string
        // Excludes null/undefined values
    }
}

// list_all_entities() — auto-pages to collect up to 1000 records
// Used for FK dropdown population (SelectAll for referenced models)
async function list_all_entities(model_name, qp = new QueryParams()) {
    let all = [];
    let page = 1;
    while (all.length < 1000) {
        qp.page = page++;
        const result = await list_entities(model_name, qp);
        all = all.concat(result.items);
        if (all.length >= result.total) break;
    }
    return all;
}

Example list request with parameters

GET /api/v1/note?page=2&page_size=10&sort=created_at&order=desc&filter[map_id]=3
Authorization: Bearer <access_token>

Custom Action Endpoints

Models can register named custom actions via ModelDefinition::add_custom_list_action(), add_custom_create_action(), and add_custom_read_action(). These appear as buttons in the frontend (implemented by executeCustomAction() in frontend/crud.js) and are dispatched to registered trigger handlers.

MethodPathScopeDescription
POST /api/v1/<model>/action/<action_name> List action Fires for the entire model collection — used for bulk operations or model-level commands (e.g. export, sync)
POST /api/v1/<model>/action/<action_name> Create action Fires during the create form (before create) — used for preview, import, or pre-populate actions
POST /api/v1/<model>/:id/action/<action_name> Read action Fires for a specific entity — used for state transitions, send, duplicate, or export of individual records

Custom action registration

// In ModelDefinition builder (C++ plugin code)
ModelDefinition("note")
    .add_custom_read_action("export_html",  "Export as HTML")
    .add_custom_read_action("duplicate",    "Duplicate note")
    .add_custom_list_action("export_all",   "Export all notes")
    .add_custom_create_action("import_md",  "Import from Markdown file")

Health, Info & Super Admin Endpoints

Operational endpoints handled by InfoHealthEndpointsGenerator and SuperAdminEndpointsGenerator.

MethodPathAuthDescription
GET /health No Liveness probe. Returns 200 OK when the server is running and the SQLite database is reachable.
GET /info No Build and runtime metadata: version string, loaded plugins, startup timestamp, non-sensitive config values.
GET /api/v1/super_admin/users SuperAdmin List all users including pending and deactivated. SuperAdmin-only.
PUT /api/v1/super_admin/users/:id SuperAdmin Modify user role or status (activate, deactivate, ban).
GET /api/v1/super_admin/jobs SuperAdmin List all scheduled jobs with their current status and next run time.
PUT /api/v1/super_admin/jobs/:id SuperAdmin Enable or disable a job, update cron expression.

GET /health — Response:

HTTP/1.1 200 OK
{
  "status": "ok",
  "db":     "ok"
}

GET /info — Response:

{
  "version":     "2.0.0",
  "plugins":     ["core", "slip_box",
                  "dictionary", "repetition"],
  "started_at":  "2026-06-26T08:00:00Z",
  "access_mode": "PublicFullAccess",
  "registration_mode": "Free"
}

Frontend Routes

Static files are served by WebEndpointsGenerator from the directory specified by --frontend-path / -s flag (default: ./frontend).

PathDescription
/web/Hive web frontend landing page
/web/index.htmlMain application — generic CRUD for all models from all plugins
/web/app_slip_box.htmlSlipbox-specific application — graph exploration view, note editor, tree navigation
/web/app_dictionary.htmlDictionary application — term search, detail view with aliases and understanding levels
/web/app_repetition.htmlRepetition review session application — card-by-card review with rating buttons
/web/js/api.jsFrontend API client module (ES module, loaded directly)
/web/js/crud.jsFrontend CRUD rendering module

Complex Filtering — Planned Enhancement

Complex JSON-based filter expressions are a planned enhancement. The design is documented in the project backlog. Currently only simple equality filters (filter[field]=value) and search query (q) are available.

Planned filter operators

OperatorJSON formatDescription
AND{"and": [expr_A, expr_B]}Logical conjunction — all conditions must match
OR{"or": [expr_A, expr_B]}Logical disjunction — at least one condition matches
NOT{"not": expr}Logical negation
eq{"field": {"eq": value}}Equality check
neq{"field": {"neq": value}}Inequality check
lt / gt{"field": {"lt": value}}Less than / greater than
lte / gte{"field": {"lte": value}}Less-or-equal / greater-or-equal
in{"field": {"in": [a, b, c]}}Value in list
like{"field": {"like": "%text%"}}SQL LIKE pattern match
is_null{"field": {"is_null": true}}Null check

Example planned complex filter

// GET /api/v1/note?filter={"and":[{"map_id":{"eq":3}},{"title":{"like":"%hive%"}}]}

{
  "and": [
    { "map_id":  { "eq": 3 } },
    { "title":   { "like": "%hive%" } },
    { "or": [
        { "parent_id": { "is_null": true } },
        { "note_order": { "gt": 0 } }
    ]}
  ]
}

Try the API live

Start Hive locally and explore the /api/v1/model_definition endpoint to see the full metadata contract. Every field in the response directly drives frontend rendering.