InventDB
API reference

The database, over HTTP.

Every InventDB instance serves this HTTP API directly: SQL, record CRUD, pre-built queries, bulk writes, vector search, index management, the relationship graph, the workflows engine and per-record attachments. Every example below is copy-paste and fully working against a real property-management dataset (the pms namespace). Your instance also serves an interactive version at /api/docs (Scalar) and /api/docs/swagger (try-it-out), plus the raw spec at /api/openapi.json.

Base URL: https://<slug>.sandbox.inventdb.com Auth: Authorization: Bearer <JWT> Get a token: POST /api/auth/login OpenAPI 3.1: /api/openapi.json
Product family. InventDB SOAR (AI Platform) is InventDB Serverless plus the AI/LLM endpoints (InventDB SOAR only). InventDB Serverless serves everything except those. Each instance’s own /api/openapi.json is filtered to its tier.

Authentication

Session and credential endpoints for InventDB. Exchange a username and password for a JWT via login, then send that token as Authorization: Bearer <token> on every protected request. get_current_user resolves the caller's identity and role (globally or per-app), change_password rotates the caller's own credential, forgot_password kicks off an out-of-band reset, and auth_health reports whether auth enforcement is on for the instance.

POST/api/auth/loginpublic InventDB SOAR + InventDB Serverless

Exchanges a username and password for a signed JWT. Use the returned token as Authorization: Bearer <token> on every subsequent protected call, and read user for the caller's id, global role, and email. On bad credentials the endpoint returns 401 with ok:false and an error message (token is null). This is the entry point for every API session; no token is required to call it.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/auth/login' \
  -H 'content-type: application/json' \
  -d '{ "username": "maya", "password": "RocketDemo-2026!" }'
{
  "username": "maya",
  "password": "RocketDemo-2026!"
}

Response 200

{
  "ok": true,
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI3ZDRlMWE5MC01MmMxLTRmMzgtOTQ5Yi0wZGVjYWZjMGZmZWUiLCJyb2xlIjoic3VwZXJhZG1pbiJ9.demo-signature-not-a-real-token",
  "user": {
    "id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "username": "maya",
    "role": "superadmin",
    "email": "maya@pmco.example",
    "isActive": true,
    "globalRole": "superadmin"
  }
}

Response 401

{
  "ok": false,
  "token": null,
  "user": null,
  "error": "Invalid username or password"
}
GET/api/auth/meBearer InventDB SOAR + InventDB Serverless

Returns the identity behind the bearer token: id, username, role, email, and active flag. Without ?app= the role field is the user's global role from _system.users. Pass ?app=<software_id> (for example ?app=pms) to get the per-app role from app_roles[<id>] instead, though a global superadmin/admin always wins (RBAC bypass). A missing or expired token yields 401.

Path & query parameters

NameInTypeDescription
appquerystringOptional app/software id, e.g. pms. When set, role is the per-app role and globalRole carries the raw global role. Empty role means the user is not provisioned for that app.

Request

curl -s 'https://acme.sandbox.inventdb.com/api/auth/me?app=pms' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "user": {
    "id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "username": "maya",
    "role": "superadmin",
    "email": "maya@pmco.example",
    "isActive": true,
    "globalRole": "superadmin"
  }
}

Response 401

{
  "ok": false,
  "user": null,
  "error": "Missing or invalid bearer token"
}
POST/api/auth/change-passwordBearer InventDB SOAR + InventDB Serverless

Rotates the password of the currently authenticated user. Supply the current_password for verification and the desired new_password; both fields are snake_case. On success the endpoint returns { "ok": true }. If the current password is wrong it returns 401, and if the user record can't be found it returns 404; both carry ok:false plus an error string.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/auth/change-password' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "current_password": "RocketDemo-2026!", "new_password": "MapleRidge-2026#PMS" }'
{
  "current_password": "RocketDemo-2026!",
  "new_password": "MapleRidge-2026#PMS"
}

Response 200

{
  "ok": true
}

Response 401

{
  "ok": false,
  "error": "Current password incorrect"
}
POST/api/auth/forgot-passwordpublic InventDB SOAR + InventDB Serverless

Requests a password-reset link for an account, identified by verified email (common path) or by username (for accounts with no email on file, relayed via cmgmt). Anonymous, no JWT required. To avoid leaking which addresses are registered, it always returns 200 with { "ok": true } whether or not a user matched; when a match is found and a delivery channel is wired, the recovery link is sent out-of-band and email_dispatched may be present.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/auth/forgot-password' \
  -H 'content-type: application/json' \
  -d '{ "email": "maya@pmco.example" }'
{
  "email": "maya@pmco.example"
}

Response 200

{
  "ok": true
}
GET/api/auth/healthpublic InventDB SOAR + InventDB Serverless

Reports whether authentication is enforced on this instance. Anonymous and cheap; use it from a client bootstrap to decide whether to prompt for login, or from a monitor to confirm the auth subsystem is up. Responds 200 with { "enabled": true } when a bearer token is required for protected routes.

Request

curl -s 'https://acme.sandbox.inventdb.com/api/auth/health'

Response 200

{
  "enabled": true
}

System & discovery

Liveness and schema-discovery endpoints. Use /health for uptime and first-run detection, then walk the data plane top-down: list the namespaces you can see, drill into the types inside one, and pull per-type record counts and storage telemetry. These are the calls the InventDB Console uses to render its Browser tab.

GET/healthpublic InventDB SOAR + InventDB Serverless

Anonymous liveness check: no token required, and callable while the server is still in setup mode. Returns the running engine version plus setupRequired, which is derived from DB state (whether an active superadmin/admin exists in _system.users) rather than from config.json. Poll this from load balancers and uptime monitors, or read setupRequired to decide whether to route a new operator into the first-run wizard.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/health'

Response 200

{
  "status": "healthy",
  "version": "0.1.0",
  "setupRequired": false,
  "tenantProduct": "ai"
}
GET/api/namespacesBearer InventDB SOAR + InventDB Serverless

Lists the namespaces (databases) the caller can see, as a bare sorted JSON array of names. RBAC filters the result to only the namespaces the caller's grants admit; superadmin and admin see everything. Pass ?includeSystem=false to hide the engine's _-prefixed system namespaces (such as _system and _soar), leaving just your application namespaces like pms.

Path & query parameters

NameInTypeDescription
includeSystemquerybooleanInclude _-prefixed system namespaces such as _system/_soar. Default true. Set false to return only application namespaces like pms.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/namespaces?includeSystem=false' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  "pms"
]

With the default includeSystem=true, the same caller (superadmin) also sees the engine's internal namespaces:

[
  "_soar",
  "_system",
  "pms"
]
GET/api/namespaces/{namespace}/typesBearer InventDB SOAR + InventDB Serverless

Lists the types (tables) inside a namespace. By default returns a bare array of type names such as ["owners", "properties", …]; pass ?metadata=true to instead get an array of {name, recordCount, …} objects. RBAC filters to the types the caller can read, and engine-internal _* types (attachments, embeddings, relationships) are hidden from non-admins.

Path & query parameters

NameInTypeDescription
namespacepathstringThe namespace to inspect, e.g. pms.
metadataquerybooleanIf true, the response is an array of TypeInfo objects ({name, recordCount, …}) instead of bare name strings. Default false.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/namespaces/pms/types' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  "compliance",
  "daily_tasks",
  "inspections",
  "leases",
  "owners",
  "properties",
  "tenants",
  "transactions",
  "vendors",
  "work_orders"
]

With ?metadata=true each entry becomes an object carrying the live record count:

[
  { "name": "owners",       "recordCount": 10,   "indexStoreCacheSizeBytes": 0 },
  { "name": "properties",   "recordCount": 50,   "indexStoreCacheSizeBytes": 0 },
  { "name": "leases",       "recordCount": 46,   "indexStoreCacheSizeBytes": 0 },
  { "name": "tenants",      "recordCount": 46,   "indexStoreCacheSizeBytes": 0 },
  { "name": "transactions", "recordCount": 2261, "indexStoreCacheSizeBytes": 0 },
  { "name": "work_orders",  "recordCount": 95,   "indexStoreCacheSizeBytes": 0 }
]
GET/api/{namespace}Bearer InventDB SOAR + InventDB Serverless

Returns every type in a namespace with its per-type record count plus storage stats: the call that powers the InventDB Console "Browser" tab. For admin/superadmin the response includes dataSizeBytes, idLocatorSizeBytes, indexSizeBytes, and totalSizeBytes. Non-admin callers get only {namespace, typeName, recordCount}; the storage telemetry is stripped. Use it to build a namespace overview or to spot which types dominate on-disk footprint.

Path & query parameters

NameInTypeDescription
namespacepathstringThe namespace to summarize, e.g. pms.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/pms' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  {
    "namespace": "pms",
    "typeName": "properties",
    "recordCount": 50,
    "dataSizeBytes": 30786,
    "idLocatorSizeBytes": 16384,
    "indexSizeBytes": 1118236,
    "totalSizeBytes": 1165406
  },
  {
    "namespace": "pms",
    "typeName": "owners",
    "recordCount": 10,
    "dataSizeBytes": 5239,
    "idLocatorSizeBytes": 8192,
    "indexSizeBytes": 352271,
    "totalSizeBytes": 365702
  },
  {
    "namespace": "pms",
    "typeName": "transactions",
    "recordCount": 2261,
    "dataSizeBytes": 1060769,
    "idLocatorSizeBytes": 307200,
    "indexSizeBytes": 8818703,
    "totalSizeBytes": 10186672
  },
  {
    "namespace": "pms",
    "typeName": "work_orders",
    "recordCount": 95,
    "dataSizeBytes": 44073,
    "idLocatorSizeBytes": 20480,
    "indexSizeBytes": 774160,
    "totalSizeBytes": 838713
  }
]

A non-admin caller sees the same rows without the size fields:

[
  { "namespace": "pms", "typeName": "properties", "recordCount": 50 },
  { "namespace": "pms", "typeName": "leases",     "recordCount": 46 }
]

SQL

Direct SQL execution via POST /sql. This is the primary read path into the InventDB engine: SELECT-only on the data plane today (writes go through db / bulk). The caller's JWT principal is enforced as a row-level-security (RLS) scope over every referenced table, and an optional ?metrics flag wraps the rows in a server-side timing envelope.

POST/sqlBearer InventDB SOAR + InventDB Serverless

Executes a SQL query against the InventDB engine and returns the matching rows. Use it for every read: filtered SELECTs, multi-table JOINs, GROUP BY aggregates, ORDER BY / LIMIT windowing, and semantic MEANING() search. RLS is applied automatically from the caller's principal (superadmin / admin / setup bypass it). Append ?metrics=1 to get a { rows, metrics } wrapper with server-side timing instead of the bare array. CRUD (INSERT / UPDATE / DELETE) is rejected here; those belong on /db/:namespace/:type/*.

Result keys mirror your SELECT list verbatim. A bare qualified column such as p.property_id is returned under the qualified key "p.property_id" (kept intact so JOINed tables that share a column name don't collide). Add an explicit alias, SELECT p.property_id AS property_id, whenever you want clean, unqualified keys in the response.

Path & query parameters

NameInTypeDescription
metricsquerystringOptional. 1 or true switches the response from a bare JSON array to a { "rows": [...], "metrics": {...} } wrapper carrying elapsedMs, count, truncated, and rowCap. Omit it (or any other value) for the legacy bare-array shape.

Request body

NameInTypeDescription
sqlbodystringRequired. The SQL text, executed verbatim under the caller's RLS scope. Identifiers are namespace.type (e.g. pms.properties). SELECT-only. MEANING(text_col) = 'phrase' / LIKE routes through the embedded MiniLM-L6-v2 semantic search (falls back to text-equality if no embedder is loaded).
parametersbodyobject | nullOptional / reserved. Placeholder for prepared-statement binds. The engine inlines literals today; put values directly in sql.

Request: filtered SELECT

curl -s -X POST 'https://acme.sandbox.inventdb.com/sql' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "sql": "SELECT property_id, city, state, type, beds, baths, market_rent, market_value, status FROM pms.properties WHERE state = '\''VA'\'' AND status = '\''Occupied'\'' ORDER BY market_rent DESC LIMIT 3" }'
{
  "sql": "SELECT property_id, city, state, type, beds, baths, market_rent, market_value, status FROM pms.properties WHERE state = 'VA' AND status = 'Occupied' ORDER BY market_rent DESC LIMIT 3"
}

Response 200: bare array (no ?metrics)

[
  {
    "property_id": "P-5012",
    "city": "Herndon",
    "state": "VA",
    "type": "Single Family",
    "beds": 3,
    "baths": 4.5,
    "market_rent": 4675,
    "market_value": 749000,
    "status": "Occupied"
  },
  {
    "property_id": "P-5001",
    "city": "Charlottesville",
    "state": "VA",
    "type": "Single Family",
    "beds": 5,
    "baths": 4,
    "market_rent": 3775,
    "market_value": 668000,
    "status": "Occupied"
  },
  {
    "property_id": "P-5045",
    "city": "Vienna",
    "state": "VA",
    "type": "Townhouse",
    "beds": 4,
    "baths": 3,
    "market_rent": 3650,
    "market_value": 720000,
    "status": "Occupied"
  }
]

Request: JOIN with the metrics envelope

curl -s -X POST 'https://acme.sandbox.inventdb.com/sql?metrics=1' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "sql": "SELECT p.property_id, p.city, l.tenant_name, l.contract_rent, l.status FROM pms.properties p JOIN pms.leases l ON l.property_id = p.property_id WHERE l.status = '\''Active'\'' ORDER BY l.contract_rent DESC LIMIT 3" }'
{
  "sql": "SELECT p.property_id, p.city, l.tenant_name, l.contract_rent, l.status FROM pms.properties p JOIN pms.leases l ON l.property_id = p.property_id WHERE l.status = 'Active' ORDER BY l.contract_rent DESC LIMIT 3"
}

Response 200: { rows, metrics } wrapper

JOIN result columns are returned under their alias.column keys (e.g. p.property_id, l.tenant_name). The metrics block reports elapsedMs (parse + plan + execute + serialize; excludes network RTT), count (rows after RLS, equal to rows.length), and the unbounded-read guard (truncated / rowCap).

{
  "rows": [
    { "p.property_id": "P-5012", "p.city": "Herndon", "l.tenant_name": "Jordan Cooper", "l.contract_rent": 4625, "l.status": "Active" },
    { "p.property_id": "P-5001", "p.city": "Charlottesville", "l.tenant_name": "Eric Coleman", "l.contract_rent": 3775, "l.status": "Active" },
    { "p.property_id": "P-5045", "p.city": "Vienna", "l.tenant_name": "Imani Edwards", "l.contract_rent": 3650, "l.status": "Active" }
  ],
  "metrics": {
    "elapsedMs": 51,
    "count": 3,
    "truncated": false,
    "rowCap": 1000
  }
}

Request: GROUP BY aggregate

curl -s -X POST 'https://acme.sandbox.inventdb.com/sql?metrics=1' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "sql": "SELECT city, COUNT(*) AS units, AVG(market_rent) AS avg_rent, SUM(market_value) AS portfolio_value FROM pms.properties GROUP BY city ORDER BY portfolio_value DESC LIMIT 5" }'
{
  "sql": "SELECT city, COUNT(*) AS units, AVG(market_rent) AS avg_rent, SUM(market_value) AS portfolio_value FROM pms.properties GROUP BY city ORDER BY portfolio_value DESC LIMIT 5"
}

Response 200: aggregated rows

Aggregates come back as JSON numbers (AVG / SUM as floats, COUNT as an integer) under the projected column alias. Note the grouped text key (city) is normalized to lowercase by the group operator.

{
  "rows": [
    { "city": "charlottesville", "units": 6, "avg_rent": 2329.1666666666665, "portfolio_value": 2566000.0 },
    { "city": "richmond",        "units": 5, "avg_rent": 1920.0,             "portfolio_value": 1878000.0 },
    { "city": "virginia beach",  "units": 6, "avg_rent": 1587.5,             "portfolio_value": 1801000.0 },
    { "city": "vienna",          "units": 3, "avg_rent": 2791.6666666666665, "portfolio_value": 1514000.0 },
    { "city": "williamsburg",    "units": 3, "avg_rent": 2491.6666666666665, "portfolio_value": 1357000.0 }
  ],
  "metrics": {
    "elapsedMs": 24,
    "count": 5,
    "truncated": false,
    "rowCap": 1000
  }
}

Error responses

StatusWhenBody
400Empty sql, malformed SQL, or a write statement (INSERT / UPDATE / DELETE); the engine returns Unsupported statement type.{ "error": "SQL error: ..." }
403RBAC / RLS denied the principal access to one or more referenced tables.{ "error": "..." }
500Engine runtime error (planner, executor, serialization).{ "error": "..." }

MCP (Model Context Protocol) Any tier

A single JSON-RPC 2.0 dispatcher at POST /mcp that lets any MCP-speaking client (Claude Desktop, Cursor, ChatGPT connectors, Gemini, or your own agent) drive the database directly. It is a peer of the SQL and REST surfaces on this page rather than a wrapper over them: the tools call the engine in-process, under the same RBAC and row-level-security scope as the caller's principal.

All 51 tools are present on both InventDB SOAR and InventDB Serverless. tools/list is not filtered by product, role, or tier: every client sees the same 51 definitions. The only product-dependent behaviour is inside a workflow step that calls an LLM (e.g. llm_extract): on InventDB Serverless that step fails when the run executes rather than when the tool is called. Reports, workflow authoring, and workflow runs themselves need no LLM and work identically on both.

Protocol & transport

Four JSON-RPC methods are served. resources/list and prompts/list answer with empty arrays (declared, unsupported); anything else returns -32601. Every response is HTTP 200, including parse and RBAC failures; the outcome lives in the JSON-RPC body, never in the status line.

POST/mcpOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

The one endpoint. Accepts a JSON-RPC 2.0 request envelope and returns a JSON-RPC 2.0 response envelope. Interactive clients authenticate with OAuth 2.1 + PKCE and dynamic client registration, with discovery at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, with the flow served from /oauth/register, /oauth/authorize, /oauth/token and /oauth/revoke. Unattended callers (CI, scripts, the examples below) may present a plain Authorization: Bearer JWT from a service account instead.

Request envelope

NameInTypeDescription
jsonrpcbodystringOptional in practice; the server does not reject a missing or mismatched value. Send "2.0".
idbodyanyCorrelation id, echoed verbatim on the response. Defaults to null when omitted.
methodbodystringRequired. One of initialize, ping, tools/list, tools/call, resources/list, prompts/list.
paramsbodyobjectMethod arguments. For tools/call this is { name, arguments }.

Request: the full envelope, once

Every per-tool example further down shows only the params object. This is the wrapper they all go inside.

curl -s -X POST 'https://acme.sandbox.inventdb.com/mcp' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get", "arguments": { "namespace": "pms", "type": "properties", "id": "P-5012" } } }'

Response 200: tool result envelope

A successful tools/call returns content (the result JSON-stringified into a text block, for models that read text) alongside structuredContent (the same value as real JSON, for code). Read structuredContent. Binary tools replace the text block with MCP resource blocks.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [ { "type": "text", "text": "{\"property_id\":\"P-5012\",\"city\":\"Herndon\",...}" } ],
    "structuredContent": {
      "property_id": "P-5012",
      "city": "Herndon",
      "state": "VA",
      "type": "Single Family",
      "beds": 3,
      "baths": 4.5,
      "market_rent": 4675,
      "market_value": 749000,
      "status": "Occupied"
    },
    "isError": false
  }
}

Two distinct failure channels

A tool that fails still returns result with isError: true; this is the MCP contract, so the model sees the error as content and can retry. A transport or protocol failure uses the JSON-RPC error channel instead. Both arrive as HTTP 200.

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [ { "type": "text", "text": "row-level security denied access to pms.owners" } ],
    "isError": true,
    "errorCode": -32001,
    "errorMessage": "row-level security denied access to pms.owners"
  }
}
CodeChannelWhen
-32700errorRequest body is not parseable JSON. id comes back null.
-32601errorUnknown method, or tools/call naming a tool that does not exist.
-32602errorparams.name missing on a tools/call.
-32001result · isErrorRBAC or RLS denied the principal.
-32002result · isErrorRecord, template, proposal, or attachment not found.
-32003result · isErrorTool executed and failed (bad SQL, expired proposal, unreadable file).
RPCinitializeOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Capability handshake. The client sends this first; the server answers with the protocol revision it implements and which capability families it advertises. Parameters are accepted and ignored; the response is constant for a given server build.

Call

{ "jsonrpc": "2.0", "id": 0, "method": "initialize", "params": {} }

Response 200

{
  "protocolVersion": "2025-03-26",
  "capabilities": {
    "tools":     { "listChanged": false },
    "resources": { "listChanged": false, "subscribe": false },
    "prompts":   { "listChanged": false },
    "logging":   {}
  },
  "serverInfo": {
    "name": "inventdb",
    "version": "0.594.0",
    "title": "InventDB — Local-First Data Layer"
  },
  "instructions": "InventDB is a schemaless, local-first data store. Use tools/list to discover available operations. ..."
}

listChanged: false on tools is load-bearing: the tool set is static for the life of the process, so a client may cache tools/list and will never receive a change notification.

RPCpingOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Liveness probe. Returns an empty result object. Useful to confirm the token is still valid and to wake an idle workspace before a first real call.

Call

{ "jsonrpc": "2.0", "id": 2, "method": "ping", "params": {} }

Response 200

{ "jsonrpc": "2.0", "id": 2, "result": {} }
RPCtools/listOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Enumerates all 51 tools with their JSON Schema inputSchema. The list is a constant: it is not filtered by product, tier, role, or namespace grants, so a read-only user and a superadmin on InventDB SOAR and InventDB Serverless alike all receive the same 51 definitions. Authorisation is applied when a tool is called rather than when it is listed.

Call

{ "jsonrpc": "2.0", "id": 3, "method": "tools/list", "params": {} }

Response 200: truncated

{
  "tools": [
    {
      "name": "schema",
      "description": "List all namespaces in this InventDB and the types (record classes) within each, with record counts. ...",
      "inputSchema": {
        "type": "object",
        "properties": {
          "include_system": { "type": "boolean", "description": "Include system namespaces (prefixed with '_'). Default false." }
        }
      }
    }
    /* … 50 more … */
  ]
}
RPCtools/callOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Invokes one tool. params.name is the tool name; params.arguments is its argument object. Every call runs as the authenticated principal: RBAC decides whether the namespace and type are reachable, RLS filters the rows, and mutations land in the audit log attributed to that user.

NameInTypeDescription
nameparamsstringRequired. One of the 51 tool names below. Unknown names return -32601.
argumentsparamsobjectTool arguments, matching that tool's inputSchema. Defaults to {} when omitted.

Call

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": { "namespace": "pms", "type": "properties", "where": { "state": "VA" }, "limit": 2 }
  }
}

Read tools 5

Discovery and retrieval. Nothing here writes. All five are RLS-scoped: an agent sees exactly the rows the connecting user can see.

TOOLschemaOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists every namespace and the types inside it with record counts. This is the intended first call of a session: it is how an agent discovers that pms exists and what lives in it. System namespaces (leading _) are omitted unless asked for.

NameTypeRequiredDescription
include_systembooleannoInclude _-prefixed system namespaces (_attachments, _reports, _system…). Default false; rarely needed.

Call

{ "name": "schema", "arguments": {} }

structuredContent

{
  "namespaces": [
    {
      "name": "pms",
      "types": [
        { "name": "properties",   "count": 48 },
        { "name": "owners",       "count": 12 },
        { "name": "tenants",      "count": 44 },
        { "name": "leases",       "count": 44 },
        { "name": "transactions", "count": 1860 },
        { "name": "work_orders",  "count": 96 },
        { "name": "vendors",      "count": 9 },
        { "name": "inspections",  "count": 61 },
        { "name": "compliance",   "count": 27 }
      ]
    }
  ]
}
TOOLdescribe_typeOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Returns the observed shape of a type by sampling real records. InventDB is schemaless: there is no declared schema, no required-field list, no enums. Everything here is inferred from data, so a property missing from the sample is not proof it never occurs. Use it before writing SQL: guessing column names is the most common agent failure.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. properties.
sample_sizeintegernoRecords to sample. Default 500, min 1, max 5000. Raise it when a type is known to be heterogeneous.

Call

{ "name": "describe_type", "arguments": { "namespace": "pms", "type": "properties", "sample_size": 500 } }

structuredContent (abridged)

{
  "namespace": "pms",
  "type": "properties",
  "sampled": 48,
  "total": 48,
  "properties": [
    { "name": "property_id",  "types": ["string"], "present_pct": 100, "distinct": 48, "samples": ["P-5012", "P-5001"] },
    { "name": "city",         "types": ["string"], "present_pct": 100, "distinct": 11, "samples": ["Herndon", "Vienna"] },
    { "name": "state",        "types": ["string"], "present_pct": 100, "distinct": 1,  "samples": ["VA"] },
    { "name": "beds",         "types": ["integer"], "present_pct": 100, "min": 1, "max": 6 },
    { "name": "baths",        "types": ["float"],   "present_pct": 100, "min": 1.0, "max": 4.5 },
    { "name": "market_rent",  "types": ["integer"], "present_pct": 100, "min": 950, "max": 4675 },
    { "name": "status",       "types": ["string"], "present_pct": 100, "distinct": 3, "samples": ["Occupied", "Vacant", "Renovation"] }
  ]
}
TOOLgetOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Fetches one record by id. Returns the record as JSON, or null when it does not exist or RLS hides it; the two cases are deliberately indistinguishable so the tool cannot be used to probe for hidden ids.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. properties.
idstringyesThe record's _id.

Call

{ "name": "get", "arguments": { "namespace": "pms", "type": "properties", "id": "P-5012" } }

structuredContent

{
  "_id": "P-5012",
  "property_id": "P-5012",
  "city": "Herndon",
  "state": "VA",
  "type": "Single Family",
  "beds": 3,
  "baths": 4.5,
  "market_rent": 4675,
  "market_value": 749000,
  "status": "Occupied"
}
TOOLsqlOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Executes a read-only SQL query with the same engine and grammar as POST /sql, including JOIN, GROUP BY, aggregates, and MEANING() semantic search. Identifiers are namespace.type. Writes are rejected: mutations go through the two-phase propose/commit tools so the user sees them before they land.

NameTypeRequiredDescription
querystringyesA SELECT statement. Note the argument is named query here, not sql as on the REST endpoint.

Call: aggregate over transactions

{
  "name": "sql",
  "arguments": {
    "query": "SELECT account_category, COUNT(*) AS n, SUM(amount) AS total FROM pms.transactions WHERE type = 'Income' GROUP BY account_category ORDER BY total DESC LIMIT 3"
  }
}

structuredContent

{
  "rows": [
    { "account_category": "rent",        "n": 1704, "total": 4192875.0 },
    { "account_category": "late fee",    "n": 96,   "total": 22176.0 },
    { "account_category": "pet rent",    "n": 60,   "total": 10800.0 }
  ],
  "count": 3
}

Call: JOIN across properties and leases

{
  "name": "sql",
  "arguments": {
    "query": "SELECT p.property_id AS property_id, p.city AS city, l.tenant_name AS tenant_name, l.contract_rent AS contract_rent FROM pms.properties p JOIN pms.leases l ON l.property_id = p.property_id WHERE l.status = 'Active' ORDER BY l.contract_rent DESC LIMIT 3"
  }
}

structuredContent

Alias every projected column. A bare qualified column such as p.property_id comes back under the literal key "p.property_id", which is awkward to consume from an agent.

{
  "rows": [
    { "property_id": "P-5012", "city": "Herndon",         "tenant_name": "Jordan Cooper", "contract_rent": 4625 },
    { "property_id": "P-5001", "city": "Charlottesville", "tenant_name": "Eric Coleman",  "contract_rent": 3775 },
    { "property_id": "P-5045", "city": "Vienna",          "tenant_name": "Imani Edwards", "contract_rent": 3650 }
  ],
  "count": 3
}

Write tools: two-phase 5

No MCP tool writes in one step. Each mutation is proposed, returning a proposal_id and a preview, and nothing changes until commit is called with that id. Proposals expire after 5 minutes. This shape is defined by the tools themselves, so it holds for every client regardless of configuration: an agent cannot silently edit data. Every committed mutation writes an audit entry, and every audit entry is reversible via undo.

TOOLpropose_insertOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Stages a new record for review. Returns a proposal_id that must be passed to commit within 5 minutes. Nothing is written by this call.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. work_orders.
payloadobjectyesThe record body. Omit _id; it is generated. Schemaless: any shape is accepted, subject to validation rules on the type.

Call

{
  "name": "propose_insert",
  "arguments": {
    "namespace": "pms",
    "type": "work_orders",
    "payload": {
      "property_id": "P-5012",
      "issue": "Main door lock jammed, tenant locked out",
      "priority": "urgent",
      "status": "Open",
      "reported_date": "2026-08-16"
    }
  }
}

structuredContent

{
  "proposal_id": "prop_7f3c1a9e",
  "operation": "insert",
  "namespace": "pms",
  "type": "work_orders",
  "preview": {
    "property_id": "P-5012",
    "issue": "Main door lock jammed, tenant locked out",
    "priority": "urgent",
    "status": "Open",
    "reported_date": "2026-08-16"
  },
  "expires_in_secs": 300
}
TOOLpropose_updateOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Stages a partial update. patch is sparse: only the fields you name change; everything else is left alone. The response carries a before/after diff so the change can be reviewed before commit.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. work_orders.
idstringyesRecord _id to patch.
patchobjectyesSparse object; only these fields change.

Call

{
  "name": "propose_update",
  "arguments": {
    "namespace": "pms",
    "type": "work_orders",
    "id": "WO-3104",
    "patch": { "status": "In Progress", "assigned_vendor_id": "V-204" }
  }
}

structuredContent

{
  "proposal_id": "prop_2b8d40f1",
  "operation": "update",
  "namespace": "pms",
  "type": "work_orders",
  "id": "WO-3104",
  "diff": {
    "status":             { "before": "Open", "after": "In Progress" },
    "assigned_vendor_id": { "before": null,   "after": "V-204" }
  },
  "expires_in_secs": 300
}
TOOLpropose_deleteOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Stages a deletion. Soft-delete semantics by default: the record is tombstoned and the audit log retains its full content, so undo can restore it.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. work_orders.
idstringyesRecord _id to delete.

Call

{ "name": "propose_delete", "arguments": { "namespace": "pms", "type": "work_orders", "id": "WO-3104" } }

structuredContent

{
  "proposal_id": "prop_9c1e77a2",
  "operation": "soft_delete",
  "namespace": "pms",
  "type": "work_orders",
  "id": "WO-3104",
  "expires_in_secs": 300
}
TOOLcommitOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Applies a pending proposal. This is the only call in the write path that changes data. It writes an audit entry whose id can later be handed to undo. A proposal older than 5 minutes returns -32003; re-propose and commit again.

NameTypeRequiredDescription
proposal_idstringyesThe id returned by a propose_* call.

Call

{ "name": "commit", "arguments": { "proposal_id": "prop_7f3c1a9e" } }

structuredContent

{
  "committed": true,
  "operation": "insert",
  "namespace": "pms",
  "type": "work_orders",
  "id": "WO-3187",
  "audit_entry_id": "aud_5510c3b7"
}

Expired proposal: isError: true

{
  "content": [ { "type": "text", "text": "proposal 'prop_7f3c1a9e' not found or expired" } ],
  "isError": true,
  "errorCode": -32003,
  "errorMessage": "proposal 'prop_7f3c1a9e' not found or expired"
}
TOOLcancelOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Discards a pending proposal without applying it. Proposals also expire on their own after 5 minutes, so this is a courtesy; use it when the user rejects a staged change so the agent's state stays honest.

NameTypeRequiredDescription
proposal_idstringyesThe id returned by a propose_* call.

Call

{ "name": "cancel", "arguments": { "proposal_id": "prop_9c1e77a2" } }

structuredContent

{ "cancelled": true, "proposal_id": "prop_9c1e77a2" }

Attachments & uploads 12

Attachment bytes never pass through the model's context unless you explicitly ask for them. attach_search answers content questions from server-side extracted text and returns signed URLs; the upload tools hand the browser a drop-zone so files go browser → server directly. Record-bound files use the parent record_id; type-level files use the reserved _vault id.

TOOLattach_readOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Fetches extracted text (never binary) for one attachment. Use it after attach_search has identified a candidate and the snippet is not enough context.

NameTypeRequiredDescription
namespacestringyese.g. pms.
att_idstringyesAttachment id from attach_search.
chunk_indexintegernoReturn only that chunk. Overrides max_chars and char_range.
char_rangeinteger[2]no[start, end] substring window into the extracted text.
max_charsintegerno500 to 200000. Default 20000.

Call

{ "name": "attach_read", "arguments": { "namespace": "pms", "att_id": "att_c81f2d05", "char_range": [0, 600] } }

structuredContent

{
  "att_id": "att_c81f2d05",
  "filename": "P-5012-roof-inspection-2026-05.pdf",
  "text": "ANNUAL ROOF INSPECTION - 1420 Sunrise Valley Dr, Herndon VA…",
  "char_range": [0, 600],
  "total_chars": 11482,
  "chunk_count": 9,
  "truncated": true
}
TOOLattach_downloadOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Returns raw bytes as an MCP resource block, for in-sandbox analysis: opening an XLSX with pandas, parsing a custom binary, rendering an image. Expensive: bytes expand roughly 1.33× under base64, so a 1 MiB cap is on the order of 1.4M output tokens. Prefer attach_read or attach_url unless you truly need the bytes.

NameTypeRequiredDescription
namespacestringyese.g. pms.
att_idstringyesAttachment id.
max_bytesintegernoHard cap. Default 1048576 (1 MiB), max 10485760 (10 MiB).
byte_rangeinteger[2]no[start, end_exclusive] slice to read just a header or footer. Still capped by max_bytes.

Call

{ "name": "attach_download", "arguments": { "namespace": "pms", "att_id": "att_44b0e7c9", "max_bytes": 262144 } }

Response 200: a resource block rather than text

{
  "content": [
    {
      "type": "resource",
      "resource": {
        "uri": "inventdb://pms/attachments/att_44b0e7c9",
        "mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "blob": "UEsDBBQABgAIAAAAIQ…"
      }
    }
  ],
  "structuredContent": { "att_id": "att_44b0e7c9", "filename": "rent-roll-2026-Q2.xlsx", "size_bytes": 188304, "bytes_returned": 188304, "truncated": false },
  "isError": false
}
TOOLattach_listOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists the attachments on one record. Equivalent to attach_search with query: "*" plus type and record_id, but cheaper and simpler when you already know the record.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. properties.
record_idstringyesParent record id, or _vault for type-level files.

Call

{ "name": "attach_list", "arguments": { "namespace": "pms", "type": "properties", "record_id": "P-5012" } }

structuredContent

{
  "attachments": [
    { "att_id": "att_c81f2d05", "filename": "P-5012-roof-inspection-2026-05.pdf", "size_bytes": 412886, "content_type": "application/pdf", "description": "Annual roof inspection" },
    { "att_id": "att_1d9a6b70", "filename": "P-5012-deed.pdf",                    "size_bytes": 96420,  "content_type": "application/pdf", "description": "Recorded deed" }
  ],
  "count": 2
}
TOOLattach_urlOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Mints a signed absolute URL the user can click in a browser. The URL embeds a one-hour token, so no bearer header is needed. Use it when you already hold an att_id and need a fresh link; attach_search already returns one per result.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. properties.
record_idstringyesParent record id.
att_idstringyesAttachment id.

Call

{ "name": "attach_url", "arguments": { "namespace": "pms", "type": "properties", "record_id": "P-5012", "att_id": "att_1d9a6b70" } }

structuredContent

{
  "att_id": "att_1d9a6b70",
  "filename": "P-5012-deed.pdf",
  "download_url": "https://acme.sandbox.inventdb.com/d/att_1d9a6b70?t=…",
  "view_url": "https://acme.sandbox.inventdb.com/d/att_1d9a6b70?t=…&inline=1",
  "expires_at_secs": 3600
}
TOOLattach_upload_urlOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

The preferred upload path for any file, local or remote. Returns a one-time browser drop-zone URL; the user drops a file and the bytes travel browser → server directly, never through the model's output tokens. Poll upload_check with the returned token to learn the outcome.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. inspections.
record_idstringnoParent record. Omit for type-level files; the server defaults to _vault.
filename_hintstringnoDefault filename if the upload does not carry one.

Call

{ "name": "attach_upload_url", "arguments": { "namespace": "pms", "type": "inspections", "record_id": "INS-2211", "filename_hint": "roof-followup.pdf" } }

structuredContent

{
  "upload_url": "https://acme.sandbox.inventdb.com/upload/u_3f9d1c47",
  "upload_token": "u_3f9d1c47",
  "expires_at_secs": 3600,
  "instructions": "Give this link to the user. It accepts one file, once."
}
TOOLbulk_upload_urlOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Same drop-zone mechanism as attach_upload_url, but the dropped file is parsed and imported as records rather than stored as an attachment. This is the preferred bulk-import path when the file is on the user's machine.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyesTarget type, e.g. transactions.
formatstringnocsv | tsv | txt | json | jsonl | xlsx | xls | xlsb | ods | auto. Auto-detected from the extension when omitted.

Call

{ "name": "bulk_upload_url", "arguments": { "namespace": "pms", "type": "transactions", "format": "csv" } }

structuredContent

{
  "upload_url": "https://acme.sandbox.inventdb.com/upload/u_8ac25e10",
  "upload_token": "u_8ac25e10",
  "expires_at_secs": 3600
}
TOOLupload_checkOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Polls an upload token issued by attach_upload_url or bulk_upload_url. Call it after handing the link to the user to find out whether the file landed and what came of it.

NameTypeRequiredDescription
upload_tokenstringyesThe token returned when the URL was minted.

Call

{ "name": "upload_check", "arguments": { "upload_token": "u_8ac25e10" } }

structuredContent

{
  "consumed": true,
  "expired": false,
  "expires_at_secs": 2841,
  "result": { "imported": 240, "type": "transactions", "namespace": "pms", "filename": "q2-ledger.csv" }
}
TOOLattach_upload_from_pathOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Uploads an attachment from a path on the server's filesystem. Only useful when the client and server share a machine (self-hosted). Base64 / inline uploads were removed deliberately: shuttling bytes through model context is slow and expensive, so attach_upload_url is the general answer.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. inspections.
record_idstringyesParent record id.
pathstringyesAbsolute path on the server.
filenamestringnoOverride the stored filename. Defaults to the basename of path.
content_typestringnoMIME type override.

Call

{
  "name": "attach_upload_from_path",
  "arguments": { "namespace": "pms", "type": "inspections", "record_id": "INS-2211", "path": "/srv/inbox/roof-followup.pdf" }
}

structuredContent

{ "att_id": "att_60fb1e83", "filename": "roof-followup.pdf", "size_bytes": 220149, "content_type": "application/pdf", "extraction_status": "Pending" }
TOOLattach_versionsOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists every version of an attachment with full per-version metadata. Versions are 1-based and immutable; re-uploading over an attachment mints a new one rather than overwriting.

NameTypeRequiredDescription
namespacestringyese.g. pms.
att_idstringyesAttachment id.

Call

{ "name": "attach_versions", "arguments": { "namespace": "pms", "att_id": "att_c81f2d05" } }

structuredContent

{
  "att_id": "att_c81f2d05",
  "current_version": 2,
  "versions": [
    { "version": 1, "filename": "P-5012-roof-inspection-2026-05.pdf", "content_type": "application/pdf", "size_bytes": 398112, "checksum": "sha256:1f0c…", "comment": "initial upload",        "created_by": "dana@acme.com", "created_at": "2026-05-14T09:21:07Z", "is_current": false },
    { "version": 2, "filename": "P-5012-roof-inspection-2026-05.pdf", "content_type": "application/pdf", "size_bytes": 412886, "checksum": "sha256:9ba7…", "comment": "signed by inspector", "created_by": "dana@acme.com", "created_at": "2026-05-19T15:02:44Z", "is_current": true }
  ]
}
TOOLattach_version_downloadOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Fetches raw bytes of a specific version, with the same delivery model as attach_download. Ask for several versions at once with versions; each becomes its own resource block, and token cost multiplies accordingly, so keep the per-version cap tight.

NameTypeRequiredDescription
namespacestringyese.g. pms.
att_idstringyesAttachment id.
versionintegernoSingle version (min 1). Mutually exclusive with versions.
versionsinteger[]noSeveral versions in one call.
max_bytes_per_versionintegernoDefault 1048576 (1 MiB), max 10485760.

Call

{ "name": "attach_version_download", "arguments": { "namespace": "pms", "att_id": "att_c81f2d05", "version": 1, "max_bytes_per_version": 524288 } }

structuredContent

{ "att_id": "att_c81f2d05", "versions_returned": [1], "bytes_returned": 398112, "truncated": false }
TOOLattach_version_promoteOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Promotes a past version to current: a non-destructive rollback. The chosen historical version is copied forward as a new latest version; nothing is deleted.

NameTypeRequiredDescription
namespacestringyese.g. pms.
att_idstringyesAttachment id.
versionintegeryesHistorical version number to promote (min 1).
commentstringnoNote recorded on the new version explaining the rollback.

Call

{ "name": "attach_version_promote", "arguments": { "namespace": "pms", "att_id": "att_c81f2d05", "version": 1, "comment": "v2 was the wrong scan" } }

structuredContent

{ "att_id": "att_c81f2d05", "promoted_from": 1, "new_version": 3, "current_version": 3 }

Bulk import 4

Three routes in, by where the data lives. Records the agent already holds go through bulk_insert (capped at 20, because each record costs output tokens). A file on the user's machine goes through bulk_upload_url. A file on the server goes through bulk_insert_previewbulk_insert_commit, which infers column types and lets the user correct them before anything is written.

TOOLbulk_insertOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Inserts a small number of records the agent already knows field by field. Hard cap of 20; each record's full JSON is emitted as part of the tool call, so this is the expensive path by design. Anything larger belongs in a file.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. transactions.
recordsobject[]yesRecord payloads, max 20. Omit _id.

Call

{
  "name": "bulk_insert",
  "arguments": {
    "namespace": "pms",
    "type": "transactions",
    "records": [
      { "property_id": "P-5012", "type": "Income", "account_category": "Rent",     "amount": 4625, "date": "2026-08-01" },
      { "property_id": "P-5012", "type": "Income", "account_category": "Late Fee", "amount": 231,  "date": "2026-08-06" }
    ]
  }
}

structuredContent

{ "inserted": 2, "namespace": "pms", "type": "transactions", "ids": ["TX-88214", "TX-88215"] }
TOOLbulk_insert_previewOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Parses a server-local file and stages it as a proposal without inserting. Returns the inferred per-column types and sample rows so the user can correct the schema before committing.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyesTarget type.
pathstringyesAbsolute server-local file path.
formatstringnocsv | tsv | txt | json | jsonl | xlsx | xls | xlsb | ods | auto.
optionsobjectnoParser options passed through (delimiter, header row, sheet name…).

Call

{
  "name": "bulk_insert_preview",
  "arguments": { "namespace": "pms", "type": "transactions", "path": "/srv/inbox/q2-ledger.csv", "format": "csv" }
}

structuredContent

{
  "proposal_id": "bulk_1a7e93c2",
  "row_count": 240,
  "inferred_schema": {
    "property_id":      "string",
    "type":             "string",
    "account_category": "string",
    "amount":           "float",
    "date":             "date"
  },
  "sample_rows": [
    { "property_id": "P-5012", "type": "Income", "account_category": "Rent", "amount": 4625.0, "date": "2026-04-01" }
  ]
}
TOOLbulk_insert_commitOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Applies a bulk_insert_preview proposal, optionally overriding the inferred column types and the strings treated as null. Pin a type when inference guessed wrong: a zero-padded account code inferred as integer, for instance, should be pinned to string.

NameTypeRequiredDescription
proposal_idstringyesFrom bulk_insert_preview.
column_typesobjectnoPer-column pins. Values: integer, float, boolean, date, datetime, string.
null_valuesstring[]noStrings treated as null across all columns. Default ["", "NULL", "null", "NA", "N/A", "-"].

Call

{
  "name": "bulk_insert_commit",
  "arguments": {
    "proposal_id": "bulk_1a7e93c2",
    "column_types": { "amount": "float", "date": "date" },
    "null_values": ["", "NULL", "-"]
  }
}

structuredContent

{ "inserted": 240, "namespace": "pms", "type": "transactions", "proposal_id": "bulk_1a7e93c2", "elapsed_ms": 612 }
TOOLbulk_insert_from_pathOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Imports a server-local file directly, skipping the preview step. Advanced: only appropriate when the path is on the same machine as the server (self-hosted) and the user supplied that path. Prefer bulk_insert_preview so the user sees inferred types first.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyesTarget type.
pathstringyesAbsolute server-side path.
formatstringnoAuto-detected from the extension when omitted.
optionsobjectnoParser options passed through.

Call

{ "name": "bulk_insert_from_path", "arguments": { "namespace": "pms", "type": "transactions", "path": "/srv/inbox/q2-ledger.csv" } }

structuredContent

{ "inserted": 240, "namespace": "pms", "type": "transactions", "format": "csv" }

Audit & undo 2

Every committed mutation is recorded and reversible. Non-admin callers see only their own entries.

TOOLaudit_logOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Returns recent mutation history. Users see only their own entries; admins see everything. Every entry id here is a valid argument to undo.

NameTypeRequiredDescription
namespacestringnoFilter by namespace.
typestringnoFilter by type.
record_idstringnoFilter to one record's history.
operationstringnoinsert | update | soft_delete | undo.
limitintegerno1 to 500. Default 50.

Call

{ "name": "audit_log", "arguments": { "namespace": "pms", "type": "work_orders", "operation": "update", "limit": 2 } }

structuredContent

{
  "entries": [
    {
      "audit_entry_id": "aud_5510c3b7",
      "operation": "update",
      "namespace": "pms",
      "type": "work_orders",
      "record_id": "WO-3104",
      "actor": "dana@acme.com",
      "at": "2026-08-16T11:04:22Z",
      "diff": { "status": { "before": "Open", "after": "In Progress" } }
    }
  ],
  "count": 1
}
TOOLundoOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Reverses a prior mutation by its audit entry id. An insert is undone by deleting the record; an update by restoring the prior state; a soft_delete by restoring the record. The undo is itself audited, so it can be undone in turn.

NameTypeRequiredDescription
audit_entry_idstringyesFrom audit_log or from a commit response.

Call

{ "name": "undo", "arguments": { "audit_entry_id": "aud_5510c3b7" } }

structuredContent

{
  "undone": true,
  "original_operation": "update",
  "namespace": "pms",
  "type": "work_orders",
  "record_id": "WO-3104",
  "new_audit_entry_id": "aud_5510c4e2"
}

Validation rules 8

Opt-in authored policy rather than inference. A rule is an expression over record that must hold for writes to a type. Authoring follows the same propose-then-save shape as data writes: rules_propose dry-runs the expression across existing records and reports how many would fail, and only rules_save persists it.

TOOLrules_listOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists validation rules. Disabled rules are hidden unless asked for.

NameTypeRequiredDescription
typestringnoRestrict to one type.
include_disabledbooleannoDefault false.

Call

{ "name": "rules_list", "arguments": { "type": "leases", "include_disabled": true } }

structuredContent

{
  "rules": [
    { "rule_id": "rule_4b21", "type": "leases", "expression": "record.contract_rent > 0",              "message": "Contract rent must be positive.",       "severity": "error",   "enabled": true },
    { "rule_id": "rule_9f07", "type": "leases", "expression": "record.lease_end > record.lease_start", "message": "Lease end must follow lease start.",    "severity": "error",   "enabled": true },
    { "rule_id": "rule_c3aa", "type": "leases", "expression": "record.deposit_held >= record.contract_rent", "message": "Deposit is below one month's rent.", "severity": "warning", "enabled": false }
  ],
  "count": 3
}
TOOLrules_proposeOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Drafts a rule and dry-runs it over existing records, returning how many rows already violate it plus a sample. Nothing is persisted; pass the returned proposal_id to rules_save to keep it.

NameTypeRequiredDescription
typestringyesType the rule applies to.
expressionstringyesBoolean expression over record, e.g. record.contract_rent > 0.
messagestringyesShown to the user when the rule fails.
severitystringnoerror blocks the write; warning allows it. Default warning.

Call

{
  "name": "rules_propose",
  "arguments": {
    "type": "leases",
    "expression": "record.deposit_held >= record.contract_rent",
    "message": "Deposit held is below one month's contract rent.",
    "severity": "warning"
  }
}

structuredContent

{
  "proposal_id": "rule_prop_77c1",
  "type": "leases",
  "expression": "record.deposit_held >= record.contract_rent",
  "severity": "warning",
  "affected_existing": 3,
  "sample_fails": [
    { "_id": "L-7020", "contract_rent": 2450, "deposit_held": 1200 },
    { "_id": "L-7033", "contract_rent": 1875, "deposit_held": 0 }
  ]
}
TOOLrules_saveOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Persists a rule drafted by rules_propose. Requires the rules scope on the token.

NameTypeRequiredDescription
proposal_idstringyesFrom rules_propose.

Call

{ "name": "rules_save", "arguments": { "proposal_id": "rule_prop_77c1" } }

structuredContent

{ "saved": true, "rule_id": "rule_c3aa", "type": "leases", "enabled": true }
TOOLrules_testOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Evaluates a saved rule against a hypothetical payload without touching data. Useful for reasoning about why a write would be rejected.

NameTypeRequiredDescription
rule_idstringyesThe rule to evaluate.
sample_payloadobjectyesHypothetical record body.

Call

{
  "name": "rules_test",
  "arguments": { "rule_id": "rule_c3aa", "sample_payload": { "contract_rent": 4625, "deposit_held": 2000 } }
}

structuredContent

{
  "rule_id": "rule_c3aa",
  "passed": false,
  "severity": "warning",
  "message": "Deposit held is below one month's contract rent.",
  "expression": "record.deposit_held >= record.contract_rent"
}
TOOLrules_for_typeOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Compact rule projection intended for form renderers: id, expression, message, severity and nothing else.

NameTypeRequiredDescription
typestringyesType to fetch rules for.

Call

{ "name": "rules_for_type", "arguments": { "type": "leases" } }

structuredContent

{
  "rules": [
    { "rule_id": "rule_4b21", "expression": "record.contract_rent > 0", "message": "Contract rent must be positive.", "severity": "error" }
  ]
}
TOOLrules_disableOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Disables a rule without deleting it. Reversible with rules_enable.

NameTypeRequiredDescription
rule_idstringyesThe rule to disable.

Call

{ "name": "rules_disable", "arguments": { "rule_id": "rule_c3aa" } }

structuredContent

{ "rule_id": "rule_c3aa", "enabled": false }
TOOLrules_enableOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Re-enables a previously disabled rule.

NameTypeRequiredDescription
rule_idstringyesThe rule to enable.

Call

{ "name": "rules_enable", "arguments": { "rule_id": "rule_c3aa" } }

structuredContent

{ "rule_id": "rule_c3aa", "enabled": true }
TOOLrules_deleteOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Hard-deletes a rule. The audit log retains its history. Prefer rules_disable unless the rule was a mistake.

NameTypeRequiredDescription
rule_idstringyesThe rule to delete.

Call

{ "name": "rules_delete", "arguments": { "rule_id": "rule_c3aa" } }

structuredContent

{ "deleted": true, "rule_id": "rule_c3aa" }

Reports 4

Report templates are HTML plus SQL; no LLM is involved in saving or rendering one, which is why all four tools work identically on InventDB Serverless. Templates are stored in the _reports system namespace and are interchangeable with those authored in the Management Console. Rendering runs the template's SQL live against your data under the caller's RLS scope.

TOOLreport_templatesOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists stored report templates by id and name. Takes no arguments.

Call

{ "name": "report_templates", "arguments": {} }

structuredContent

{
  "templates": [
    { "_id": "tpl_owner_summary", "name": "Owner Portfolio Summary" },
    { "_id": "tpl_rent_roll",     "name": "Monthly Rent Roll" }
  ],
  "count": 2
}
TOOLreport_template_getOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Fetches one template in full, including its HTML and bindings. Read a template before editing it; report_template_save replaces rather than merges.

NameTypeRequiredDescription
idstringyesTemplate id.

Call

{ "name": "report_template_get", "arguments": { "id": "tpl_rent_roll" } }

structuredContent (abridged)

{
  "_id": "tpl_rent_roll",
  "name": "Monthly Rent Roll",
  "htmlTemplate": "<h1>Rent roll</h1>\n<table><thead>…</thead><tbody>{{TABLE:rows}}</tbody></table>",
  "queries": [
    { "placeholder": "rows", "sql": "SELECT property_id AS \"Property\", tenant_name AS \"Tenant\", contract_rent AS \"Rent\" FROM pms.leases WHERE status = 'Active' ORDER BY contract_rent DESC" }
  ],
  "parameters": [],
  "placeholders": ["rows"]
}
TOOLreport_template_saveOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Requires admin or superadmin. Creates or updates a template. Two authoring shapes, mixable in one document: <script type="server"> blocks (full JS, needed for charts, conditional sections and custom formatting) and {{placeholder}} markers backed by a bindings map (quick KPIs and flat tables). Only {{name}} and {{TABLE:name}} are supported; {{SCALAR:}}, {{JSON:}} and {{#each}} render as literal text.

NameTypeRequiredDescription
namestringyesDisplay name.
htmlstringyesHTML body. Wrapped in a print-ready skeleton with @page CSS if you do not supply <html>.
bindingsobjectno{placeholder: SQL}. Only needed for {{placeholder}} markers; server scripts query directly.
idstringnoExisting id to update. Omit to create.

Call

{
  "name": "report_template_save",
  "arguments": {
    "name": "Monthly Rent Roll",
    "html": "<h1>Rent roll: active leases</h1>\n<p>Total monthly: ${{total_rent}}</p>\n<table>\n  <thead><tr><th>Property</th><th>Tenant</th><th>Rent</th></tr></thead>\n  <tbody>{{TABLE:rows}}</tbody>\n</table>",
    "bindings": {
      "total_rent": "SELECT SUM(contract_rent) AS value FROM pms.leases WHERE status = 'Active'",
      "rows": "SELECT property_id AS \"Property\", tenant_name AS \"Tenant\", contract_rent AS \"Rent\" FROM pms.leases WHERE status = 'Active' ORDER BY contract_rent DESC"
    }
  }
}

structuredContent

{ "saved": true, "id": "tpl_rent_roll", "name": "Monthly Rent Roll", "placeholders": ["total_rent", "rows"] }

A non-admin caller gets isError: true with -32001 and the message “report template authoring currently requires admin role”.

TOOLreport_renderOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Renders a saved template to print-ready HTML, running its SQL live. parameters supply values for :name references in the bindings (no leading colon in the key). A missing parameter causes the surrounding WHERE field op :name clause to be stripped, which is how a template is made to render both filtered and unfiltered. The response reports what did not resolve, so an agent can iterate.

NameTypeRequiredDescription
template_idstringyesTemplate to render.
parametersobjectnoValues for :name references in the template's SQL.

Call

{
  "name": "report_render",
  "arguments": { "template_id": "tpl_owner_summary", "parameters": { "owner_id": "O-1002" } }
}

structuredContent

{
  "template_id": "tpl_owner_summary",
  "html": "<html><head>…</head><body><h1>Owner portfolio O-1002</h1>…</body></html>",
  "recognised_parameters": ["owner_id"],
  "unused_parameters": [],
  "unresolved_placeholders": [],
  "unbound_html_placeholders": [],
  "binding_errors": []
}

Workflows 7

Read, run, version and activate event-driven workflows. All seven are available on both products: authoring, listing and running a workflow needs no LLM, so these were un-gated on InventDB Serverless in v0.501 to match the REST surface. The single product-dependent case is a step that calls an LLM (such as llm_extract): on InventDB Serverless that step fails when the run reaches it, and workflow_runs reports the failure. Authoring a workflow from scratch is a REST operation; see Workflows.

TOOLworkflows_listOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists the caller's workflows as a light projection: enough to choose one rather than the full plan. Use workflow_get for the steps.

NameTypeRequiredDescription
include_deletedbooleannoInclude soft-deleted workflows. Default false.

Call

{ "name": "workflows_list", "arguments": {} }

structuredContent

{
  "workflows": [
    {
      "_id": "wf_owner_stmt",
      "name": "Daily Owner Statements: 9 AM email to each owner",
      "trigger_kind": "cron",
      "trigger_intent": "Every day at 09:00",
      "active": true,
      "pending_approval": false,
      "created_at": "2026-06-02T08:14:00Z",
      "updated_at": "2026-08-11T17:40:12Z"
    }
  ],
  "count": 1
}
TOOLworkflow_getOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Fetches one workflow's full definition: trigger kind, spec and intent, the ordered plan steps, the current version, and runtime state.

NameTypeRequiredDescription
workflow_idstringyesWorkflow id.

Call

{ "name": "workflow_get", "arguments": { "workflow_id": "wf_owner_stmt" } }

structuredContent (abridged)

{
  "_id": "wf_owner_stmt",
  "name": "Daily Owner Statements: 9 AM email to each owner",
  "trigger": { "kind": "cron", "spec": "0 9 * * *", "intent": "Every day at 09:00" },
  "plan": [
    { "step": 1, "tool": "sql_query",     "label": "Fetch all owners",                "sql": "SELECT owner_id, email, name FROM pms.owners" },
    { "step": 2, "tool": "render_report", "label": "Render Owner Portfolio Summary",  "template_id": "tpl_owner_summary", "foreach": "owners" },
    { "step": 3, "tool": "send_email",    "label": "Email each owner their statement" }
  ],
  "version": 7,
  "active": true,
  "sandbox": false,
  "pending_approval": false
}
TOOLworkflow_versionsOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists version history, latest first, plus the current_version pointer. Every save and every rollback mints a version; older versions are read-only.

NameTypeRequiredDescription
workflow_idstringyesWorkflow id.

Call

{ "name": "workflow_versions", "arguments": { "workflow_id": "wf_owner_stmt" } }

structuredContent

{
  "current_version": 7,
  "versions": [
    { "version": 7, "created_at": "2026-08-11T17:40:12Z", "note": "rollback to v5" },
    { "version": 6, "created_at": "2026-08-10T09:02:41Z", "note": "added late-fee section" },
    { "version": 5, "created_at": "2026-07-28T14:19:03Z", "note": "" }
  ]
}
TOOLworkflow_runsOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists recent runs with status, timestamps, error and whether the run was sandboxed. Read-only. Because workflow_run is asynchronous, this is how you learn the outcome.

NameTypeRequiredDescription
workflow_idstringyesWorkflow id.

Call

{ "name": "workflow_runs", "arguments": { "workflow_id": "wf_owner_stmt" } }

structuredContent

{
  "runs": [
    { "run_id": "run_9d21", "status": "succeeded", "sandbox": false, "started_at": "2026-08-16T09:00:02Z", "ended_at": "2026-08-16T09:00:19Z", "error": null },
    { "run_id": "run_9c88", "status": "failed",    "sandbox": false, "started_at": "2026-08-15T09:00:02Z", "ended_at": "2026-08-15T09:00:05Z", "error": "step 2: template 'tpl_owner_summary' not found" }
  ],
  "count": 2
}
TOOLworkflow_runOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Fires a workflow once, by hand. Queues the run and returns an event_id immediately; execution is asynchronous, so poll workflow_runs for the outcome. Two optional modifiers: pin an older version, or force sandbox for this run only so side-effects are mocked.

NameTypeRequiredDescription
workflow_idstringyesWorkflow id.
versionintegernoRun this version's frozen plan instead of the current one.
sandboxbooleannoForce this run to be sandboxed (mock side-effects) for this run only.

Call

{ "name": "workflow_run", "arguments": { "workflow_id": "wf_owner_stmt", "sandbox": true } }

structuredContent

{ "queued": true, "workflow_id": "wf_owner_stmt", "event_id": "evt_3ba07f12", "sandbox": true }
TOOLworkflow_rollbackOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Rolls a workflow back to a previous version. Non-destructive: it mints a new latest version that copies the chosen old plan, so nothing in the history is lost.

NameTypeRequiredDescription
workflow_idstringyesWorkflow id.
versionintegeryesExisting version number to roll back to.

Call

{ "name": "workflow_rollback", "arguments": { "workflow_id": "wf_owner_stmt", "version": 5 } }

structuredContent

{ "workflow_id": "wf_owner_stmt", "rolled_back_to": 5, "new_version": 8, "current_version": 8 }
TOOLworkflow_set_activeOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Activates a workflow so it fires on its trigger, or pauses it. Independent of the sandbox flag; an active workflow can still be sandboxed, in which case it runs on schedule but mocks its side-effects.

NameTypeRequiredDescription
workflow_idstringyesWorkflow id.
activebooleanyestrue = go live on the trigger; false = pause.

Call

{ "name": "workflow_set_active", "arguments": { "workflow_id": "wf_owner_stmt", "active": false } }

structuredContent

{ "workflow_id": "wf_owner_stmt", "active": false }

Forms 4

Data-entry helpers. Instead of an agent interviewing the user field by field, it can hand over a real HTML form with FK typeahead and rule-aware validation. analyze_type_for_formsave_form_templaterender_record_form is the authoring path; render_record_form alone auto-generates a basic form.

TOOLanalyze_type_for_formOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Returns the schema, FK relationships, cascade dependencies and display-field hints for a type: which fields need a lookup, which cascade from another, and what to show in a picker. Call this before authoring a form template.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. work_orders.

Call

{ "name": "analyze_type_for_form", "arguments": { "namespace": "pms", "type": "work_orders" } }

structuredContent (abridged)

{
  "namespace": "pms",
  "type": "work_orders",
  "fields": [
    { "name": "property_id", "type": "string", "is_fk": true,  "references": "pms.properties", "display_field": "property_id" },
    { "name": "issue",       "type": "string", "is_fk": false },
    { "name": "priority",    "type": "string", "is_fk": false, "observed_values": ["low", "normal", "urgent"] },
    { "name": "status",      "type": "string", "is_fk": false, "observed_values": ["Open", "In Progress", "Closed"] }
  ],
  "rules": [ { "rule_id": "rule_wo1", "expression": "record.issue != ''", "message": "Describe the issue.", "severity": "error" } ]
}
TOOLsave_form_templateOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Saves a reusable form template. The HTML uses {{field:…}} markers for inputs and {{lookup:…}} for FK typeahead. Note the argument is typeName here, not type as in most other tools.

NameTypeRequiredDescription
namestringyesTemplate display name.
namespacestringyese.g. pms.
typeNamestringyesTarget type, e.g. work_orders.
htmlTemplatestringyesHTML with {{placeholder}} syntax.
styleobjectno{ primaryColor, fontFamily }.

Call

{
  "name": "save_form_template",
  "arguments": {
    "name": "New work order",
    "namespace": "pms",
    "typeName": "work_orders",
    "htmlTemplate": "

Report an issue

\n{{lookup:property_id|label=Property|from=pms.properties|display=property_id}}\n{{field:issue|label=What is wrong?|type=text|required}}\n{{field:priority|label=Priority|type=select|options=low,normal,urgent}}", "style": { "primaryColor": "#6A46C1" } } }

structuredContent

{ "saved": true, "template_id": "form_wo_new", "name": "New work order", "namespace": "pms", "typeName": "work_orders" }
TOOLlist_form_templatesOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Lists saved form templates with their ids, names and target types. Takes no arguments.

Call

{ "name": "list_form_templates", "arguments": {} }

structuredContent

{
  "templates": [
    { "template_id": "form_wo_new", "name": "New work order", "namespace": "pms", "typeName": "work_orders" }
  ],
  "count": 1
}
TOOLrender_record_formOAuth 2.1 / Bearer InventDB SOAR + InventDB Serverless

Returns a self-contained HTML form. Template mode (preferred) renders a saved template with FK typeahead, cascade filters and SQL validation; find one with list_form_templates. Auto mode (namespace + type, no template_id) generates a basic form from the observed schema. Pass id to pre-populate for an edit.

NameTypeRequiredDescription
namespacestringyese.g. pms.
typestringyese.g. work_orders.
template_idstringnoSaved form template. Omit for auto mode.
idstringnoRecord id for edit mode. Omit to insert.

Call

{
  "name": "render_record_form",
  "arguments": { "namespace": "pms", "type": "work_orders", "template_id": "form_wo_new" }
}

structuredContent

{
  "html": "
", "mode": "template", "template_id": "form_wo_new", "namespace": "pms", "type": "work_orders" }

Record CRUD (db)

Record CRUD over /db/:namespace/:type[/:id]. Insert, read, update, and delete individual documents in any type, plus a cheap id-listing walk. The engine is schemaless: new properties and new types are created on first write, no migration step. Writes stamp _id, _createdAt, _createdBy, _updatedAt, and _updatedBy from the JWT principal, validate _relationships referential integrity (409 on conflict), and enforce row-level security so you only touch rows your grants admit.

POST/db/{namespace}/{type}Bearer InventDB SOAR + InventDB Serverless

Insert a single document into :namespace.:type. The full JSON body is the document; there is no envelope. The engine assigns _id (UUID v4 unless you supply one) and stamps the created/updated audit fields from your token. New namespaces and types are created lazily on first write, and any new property is indexed on first sight. Indexing is not optional: every write updates the property and vector indexes before it returns, so reads are consistent immediately. Plan large loads around that cost.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms. Created lazily if new.
typepathstringe.g. properties. Created lazily if new.
disableIndexingquerybooleanAccepted and ignored; indexing cannot be disabled.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/db/pms/properties' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{
    "property_id": "P-5099",
    "owner_id": "O-1002",
    "street": "88 Maple Ct",
    "city": "Charlottesville",
    "state": "VA",
    "zip": 22902,
    "region": "Central VA",
    "type": "Condo",
    "beds": 2,
    "baths": 2.5,
    "sqft": 963,
    "year_built": 1990,
    "market_rent": 1525,
    "market_value": 253000,
    "acq_date": "2008-02-27",
    "status": "Occupied"
  }'
{
  "property_id": "P-5099",
  "owner_id": "O-1002",
  "street": "88 Maple Ct",
  "city": "Charlottesville",
  "state": "VA",
  "zip": 22902,
  "region": "Central VA",
  "type": "Condo",
  "beds": 2,
  "baths": 2.5,
  "sqft": 963,
  "year_built": 1990,
  "market_rent": 1525,
  "market_value": 253000,
  "acq_date": "2008-02-27",
  "status": "Occupied"
}

Response 201

{
  "ok": true,
  "id": "d5f17a05-5806-48bf-88ab-e9a586bd06b9"
}
PUT/db/{namespace}/{type}Bearer InventDB SOAR + InventDB Serverless

Update an existing document by _id, which MUST be present in the request body. The body you send becomes the new latest version (append-only at the storage layer; older versions stay readable via point-in-time queries). Row-level filters must accept both the pre-image and the post-image: you can neither edit a row you don't own nor move one out of your ownership scope. Missing _id is 400; no matching row is 404; a referential-integrity conflict is 409.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms.
typepathstringe.g. properties.
disableIndexingquerybooleanAccepted and ignored; indexing cannot be disabled.

Request

curl -s -X PUT 'https://acme.sandbox.inventdb.com/db/pms/properties' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{
    "_id": "d5f17a05-5806-48bf-88ab-e9a586bd06b9",
    "property_id": "P-5099",
    "owner_id": "O-1002",
    "market_rent": 1595,
    "status": "Vacant"
  }'
{
  "_id": "d5f17a05-5806-48bf-88ab-e9a586bd06b9",
  "property_id": "P-5099",
  "owner_id": "O-1002",
  "market_rent": 1595,
  "status": "Vacant"
}

Response 200

{
  "ok": true,
  "id": "d5f17a05-5806-48bf-88ab-e9a586bd06b9"
}
GET/db/{namespace}/{type}/{id}Bearer InventDB SOAR + InventDB Serverless

Fetch a single document by _id. The response body is the document JSON itself with no envelope, including the system fields _id, _createdAt, and _updatedAt. RLS row filters apply: a row outside your scope returns 404 rather than 403, so the API never leaks whether an id you can't see exists.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms.
typepathstringe.g. properties.
idpathstringDocument _id, e.g. 2bba668e-ad1c-447b-9e70-7cf9d9221b2c.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/db/pms/properties/2bba668e-ad1c-447b-9e70-7cf9d9221b2c' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "property_id": "P-5009",
  "owner_id": "O-1001",
  "street": "5247 Heritage Park Dr",
  "city": "Virginia Beach",
  "state": "VA",
  "zip": 23451,
  "region": "Hampton Rds",
  "type": "Townhouse",
  "beds": 3,
  "baths": 2,
  "sqft": 1181,
  "year_built": 1968,
  "lot_ac": 0,
  "garage": "None",
  "market_rent": 1550,
  "market_value": 299000,
  "acq_date": "2009-07-05",
  "acq_price": 235000,
  "annual_tax": 3000,
  "annual_insurance": 1050,
  "monthly_hoa": 165,
  "status": "Occupied",
  "_id": "2bba668e-ad1c-447b-9e70-7cf9d9221b2c",
  "_createdAt": "2026-07-10T02:16:36.446031800+00:00",
  "_updatedAt": "2026-07-10T02:16:36.446031800+00:00"
}
DELETE/db/{namespace}/{type}/{id}Bearer InventDB SOAR + InventDB Serverless

Delete a document by _id. This is a soft delete: a tombstone supersedes the row for normal SELECTs, but earlier versions stay queryable for point-in-time reads. Any attachments owned by this _id are removed transactionally. A row protected by referential integrity (referenced by another type via _relationships) cannot be deleted until those references clear, and the call returns 409.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms.
typepathstringe.g. properties.
idpathstringDocument _id to delete.

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/db/pms/properties/d5f17a05-5806-48bf-88ab-e9a586bd06b9' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true
}
GET/db/{namespace}/{type}Bearer InventDB SOAR + InventDB Serverless

List all document _ids in a type. This is cheap: it walks the per-type id index only and reads no property values. RLS row filters still apply, so you only see the ids of rows you're allowed to read. For paged fetches of full rows, use POST /sql with LIMIT and ORDER BY instead of hydrating every id here.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms.
typepathstringe.g. properties.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/db/pms/properties' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  "01c8183d-a16e-4612-91bd-13ce37774d59",
  "01e774f9-3c03-4a1b-9754-935e93d47df3",
  "0255a1f8-04be-4dc7-b33b-54c0ea590ab0",
  "07068d7b-c633-4312-b403-2c169c8b487a",
  "16498c30-9e60-4e8f-b79b-c443b48bc42b",
  "2bba668e-ad1c-447b-9e70-7cf9d9221b2c"
]

Pre-built queries

Convenience query wrappers that cover the most common single-type read patterns (substring search, numeric ranges, date ranges, and AND-composed multi-property filters) without hand-writing SQL. Each is a thin shim over the engine's SQL path (RLS row filters and per-table grants still apply), and every one returns a bare JSON array of full row objects. For OR semantics, joins, aggregation, or semantic MEANING() search, use POST /sql directly.

GET/query/{namespace}/{type}/containsBearer InventDB SOAR + InventDB Serverless

Substring search on a single string property. This is a convenience wrapper over SELECT * FROM ns.type WHERE prop LIKE '%q%'. It is fast on small types; for larger types prefer /sql with an explicit LIMIT. Use it for quick lookups like finding an owner by part of their name or a work order by a keyword in the issue text.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringType / table name, e.g. owners
propquerystringString property to scan, e.g. name
qquerystringSubstring to match (LIKE-style), e.g. Whitfield

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/query/pms/owners/contains?prop=name&q=Whitfield' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  {
    "owner_id": "O-1004",
    "name": "Robert & Susan Whitfield",
    "type": "Joint",
    "contact": "Robert Whitfield",
    "phone": "(434) 555-5557",
    "email": "robert.whitfield97@example.com",
    "tax_id": "**-***3615",
    "w_9_on_file": true,
    "mailing_address": "7024 Patriot Way, Norfolk, VA 20191",
    "mgmt_fee": 0.09,
    "payout_method": "Check",
    "_id": "b9b11a69-608c-4434-82ed-363e80efa45b",
    "_createdAt": "2026-07-10T02:16:37.399111700+00:00",
    "_updatedAt": "2026-07-10T02:16:37.399111700+00:00"
  }
]
GET/query/{namespace}/{type}/rangeBearer InventDB SOAR + InventDB Serverless

Closed-interval numeric range query: lo <= prop <= hi. Both bounds are inclusive doubles. Use it to pull rows where a numeric column falls inside a window, for example properties whose market_rent is between two dollar amounts, or work orders in a cost band. Returns a bare array of full row objects.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringType / table name, e.g. properties
propquerystringNumeric property to range over, e.g. market_rent
loquerynumberInclusive lower bound, e.g. 1500
hiquerynumberInclusive upper bound, e.g. 1600

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/query/pms/properties/range?prop=market_rent&lo=1500&hi=1600' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  {
    "property_id": "P-5033",
    "owner_id": "O-1002",
    "street": "8033 Willow Brook Pl",
    "city": "Charlottesville",
    "state": "VA",
    "zip": 22902,
    "region": "Central VA",
    "type": "Condo",
    "beds": 2,
    "baths": 2.5,
    "sqft": 963,
    "year_built": 1990,
    "market_rent": 1525,
    "market_value": 253000,
    "acq_date": "2008-02-27",
    "annual_tax": 2300,
    "annual_insurance": 825,
    "monthly_hoa": 285,
    "status": "Occupied",
    "_id": "260a5a18-8fd0-4c99-8808-3ae773cf0173",
    "_createdAt": "2026-07-10T02:16:36.446031800+00:00",
    "_updatedAt": "2026-07-10T02:16:36.446031800+00:00"
  },
  {
    "property_id": "P-5009",
    "owner_id": "O-1001",
    "street": "5247 Heritage Park Dr",
    "city": "Virginia Beach",
    "state": "VA",
    "zip": 23451,
    "region": "Hampton Rds",
    "type": "Townhouse",
    "beds": 3,
    "baths": 2,
    "sqft": 1181,
    "year_built": 1968,
    "market_rent": 1550,
    "market_value": 299000,
    "acq_date": "2009-07-05",
    "status": "Occupied",
    "_id": "2bba668e-ad1c-447b-9e70-7cf9d9221b2c",
    "_createdAt": "2026-07-10T02:16:36.446031800+00:00",
    "_updatedAt": "2026-07-10T02:16:36.446031800+00:00"
  }
]
GET/query/{namespace}/{type}/daterangeBearer InventDB SOAR + InventDB Serverless

Date / DateTime range query with inclusive bounds. Bounds are ISO 8601 strings; date-only literals are treated as midnight UTC, so to capture the entire end day pass a full timestamp such as end=2026-10-31T23:59:59Z. Use it for date-window reports like leases starting in a given month or transactions posted in a quarter. Returns a bare array of full row objects.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringType / table name, e.g. leases
propquerystringDate / datetime property to filter, e.g. lease_start
startquerystringInclusive start (ISO 8601), e.g. 2025-10-01
endquerystringInclusive end (ISO 8601), e.g. 2025-10-31

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/query/pms/leases/daterange?prop=lease_start&start=2025-10-01&end=2025-10-31T23:59:59Z' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

[
  {
    "lease_id": "L-7018",
    "property_id": "P-5020",
    "tenant_id": "T-9018",
    "tenant_name": "Emily Brooks",
    "lease_start": "2025-10-20",
    "lease_end": "2026-10-15",
    "term_mo": 12,
    "market_rent": 2675,
    "contract_rent": 2725,
    "deposit_held": 2725,
    "late_fee": 136,
    "grace_days": 5,
    "renewal_type": "Auto-renew",
    "status": "Active",
    "_id": "72a1ca6a-6964-445a-bf43-1921f011515d",
    "_createdAt": "2026-07-10T02:16:37.753951200+00:00",
    "_updatedAt": "2026-07-10T02:16:37.753951200+00:00"
  },
  {
    "lease_id": "L-7043",
    "property_id": "P-5046",
    "tenant_id": "T-9043",
    "tenant_name": "Ryan Clark",
    "lease_start": "2025-10-24",
    "lease_end": "2026-10-19",
    "term_mo": 12,
    "market_rent": 1850,
    "contract_rent": 1850,
    "deposit_held": 1850,
    "late_fee": 92,
    "grace_days": 5,
    "renewal_type": "Auto-renew",
    "status": "Active",
    "_id": "c98d5f7f-16e2-42f2-87d8-2bb8b3cae986",
    "_createdAt": "2026-07-10T02:16:37.753951200+00:00",
    "_updatedAt": "2026-07-10T02:16:37.753951200+00:00"
  }
]
POST/query/{namespace}/{type}/filterBearer InventDB SOAR + InventDB Serverless

AND-composed multi-property filter with pagination and ordering: a convenience over /sql for the very common "filter on N properties, sort, paginate" case. Each entry in conditions becomes one WHERE prop OP value clause and all clauses are AND'ed together; for OR semantics use /sql directly. Sort with order_by + order_dir and page with limit / offset. Returns a bare array of full row objects.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringType / table name, e.g. properties
conditionsbodyarrayRequired. AND'ed list of { prop, op, value }. Empty list returns all readable rows (subject to RLS).
conditions[].opbodystringOne of eq, ne, gt, gte, lt, lte, like (SQL LIKE), contains (case-sensitive substring).
order_bybodystring?Property to sort by, e.g. market_rent.
order_dirbodystring?asc (default) or desc, case-insensitive.
limitbodyinteger?Max rows. No cap enforced if absent; use with care.
offsetbodyinteger?Skip this many rows after order_by is applied.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/query/pms/leases/filter' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "conditions": [ { "prop": "status", "op": "eq", "value": "Active" }, { "prop": "contract_rent", "op": "gte", "value": 2500 } ], "order_by": "contract_rent", "order_dir": "desc", "limit": 2, "offset": 0 }'
{
  "conditions": [
    { "prop": "status", "op": "eq", "value": "Active" },
    { "prop": "contract_rent", "op": "gte", "value": 2500 }
  ],
  "order_by": "contract_rent",
  "order_dir": "desc",
  "limit": 2,
  "offset": 0
}

Response 200

[
  {
    "lease_id": "L-7012",
    "property_id": "P-5012",
    "tenant_id": "T-9012",
    "tenant_name": "Jordan Cooper",
    "lease_start": "2025-10-26",
    "lease_end": "2026-10-21",
    "term_mo": 12,
    "market_rent": 4675,
    "contract_rent": 4625,
    "deposit_held": 4625,
    "late_fee": 231,
    "grace_days": 5,
    "renewal_type": "Auto-renew",
    "status": "Active",
    "_id": "c129df1c-73b9-4339-8fc8-ad271fd505e8",
    "_createdAt": "2026-07-10T02:16:37.753951200+00:00",
    "_updatedAt": "2026-07-10T02:16:37.753951200+00:00"
  },
  {
    "lease_id": "L-7001",
    "property_id": "P-5001",
    "tenant_id": "T-9001",
    "tenant_name": "Eric Coleman",
    "lease_start": "2026-04-04",
    "lease_end": "2027-03-30",
    "term_mo": 12,
    "market_rent": 3775,
    "contract_rent": 3775,
    "deposit_held": 3775,
    "late_fee": 189,
    "grace_days": 5,
    "renewal_type": "Auto-renew",
    "status": "Active",
    "_id": "01ba54c2-728a-4357-b184-0bff550400cd",
    "_createdAt": "2026-07-10T02:16:37.753951200+00:00",
    "_updatedAt": "2026-07-10T02:16:37.753951200+00:00"
  }
]

Bulk operations

High-throughput, whole-batch operations against a type: insert many documents in one round-trip, and remove rows en masse by id, by filter, or all at once. Destructive endpoints layer on guard rails (referential-integrity checks, an explicit CONFIRM phrase, and admin-only type/namespace drops) so a single call can never quietly cascade.

POST/api/{namespace}/{type}/bulkBearer InventDB SOAR + InventDB Serverless

Insert N documents in a single request instead of one POST per row. Accepts either a bare JSON array or an { "documents": [...] } envelope. RBAC and the writer's row-filter are validated once up front, then the batch streams into the engine; per-row failures are aggregated into error while the ids that landed are still returned. A successful insert responds 201 Created; an empty request (zero documents) is a 200 no-op. Indexing always runs as part of the write and cannot be turned off, so size large loads around that cost.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms. Created lazily on first write.
typepathstringe.g. properties. Created lazily on first write.
disableIndexingquerybooleanAccepted and ignored; indexing cannot be disabled. Every write indexes synchronously; budget for that on large loads.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/pms/properties/bulk' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '[
    { "property_id": "P-5033", "owner_id": "O-1002", "street": "418 Rugby Rd", "city": "Charlottesville", "state": "VA", "zip": 22903, "type": "Condo", "beds": 2, "baths": 2.5, "sqft": 963, "market_rent": 1525, "market_value": 253000, "status": "occupied" },
    { "property_id": "P-5034", "owner_id": "O-1004", "street": "77 Elliott Ave", "city": "Charlottesville", "state": "VA", "zip": 22902, "type": "Townhome", "beds": 3, "baths": 2.0, "sqft": 1480, "market_rent": 1975, "market_value": 312000, "status": "vacant" }
  ]'
{
  "documents": [
    { "property_id": "P-5033", "owner_id": "O-1002", "street": "418 Rugby Rd", "city": "Charlottesville", "state": "VA", "zip": 22903, "type": "Condo", "beds": 2, "baths": 2.5, "sqft": 963, "market_rent": 1525, "market_value": 253000, "status": "occupied" },
    { "property_id": "P-5034", "owner_id": "O-1004", "street": "77 Elliott Ave", "city": "Charlottesville", "state": "VA", "zip": 22902, "type": "Townhome", "beds": 3, "baths": 2.0, "sqft": 1480, "market_rent": 1975, "market_value": 312000, "status": "vacant" }
  ]
}

Response 201

{
  "ok": true,
  "insertedCount": 2,
  "ids": [
    "4e7d7e56-a14c-47d9-acf1-d6b9187f94f6",
    "12b2083a-e03e-4c35-8a79-89068fcd5676"
  ],
  "error": null
}
POST/api/{namespace}/{type}/deleteManyBearer InventDB SOAR + InventDB Serverless

Delete a set of documents by _id in one call. Referential integrity is validated for the entire batch before any row is removed: if any id is referenced by another type via _relationships, the whole call aborts with 409 and nothing is deleted. Ids that don't exist are skipped silently, so deletedCount may be less than ids.length. Returns 206 if some individual deletes errored after the RI gate passed.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. work_orders

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/pms/work_orders/deleteMany' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "ids": ["93906841-8a89-4310-9d61-e056bf72c330", "cc32867d-d243-444f-89ba-57f6c546172d"] }'
{
  "ids": [
    "93906841-8a89-4310-9d61-e056bf72c330",
    "cc32867d-d243-444f-89ba-57f6c546172d"
  ]
}

Response 200

{
  "ok": true,
  "deletedCount": 1,
  "error": null
}
POST/api/{namespace}/{type}/deleteBearer InventDB SOAR + InventDB Serverless

Delete every row that matches a filter condition. The engine runs a SELECT _id with the filter applied, then deletes each matched row. The filter is a property→value map whose terms are AND'ed into a SQL WHERE clause; e.g. {"status": "void", "year": 2024} becomes WHERE status = 'void' AND year = 2024. Safer than deleteAll because the filter shape forces an explicit decision about what to drop.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. transactions

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/pms/transactions/delete' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "filter": { "type": "Expense", "account_category": "Property Tax", "property_id": "P-5033" } }'
{
  "filter": {
    "type": "Expense",
    "account_category": "Property Tax",
    "property_id": "P-5033"
  }
}

Response 200

{
  "ok": true,
  "deletedCount": 3,
  "error": null
}
POST/api/{namespace}/{type}/deleteAllBearer InventDB SOAR + InventDB Serverless

Delete every row in a type: a truncate. The request body must carry the literal confirmation phrase "CONFIRM"; anything else refuses with 400 to guard against accidental cascades. The type folder, its properties, and history rows remain (use DELETE /api/:ns/:type to drop the type entirely). Requires delete access on the type.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. leases

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/pms/leases/deleteAll' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "confirm": "CONFIRM" }'
{
  "confirm": "CONFIRM"
}

Response 200

{
  "ok": true,
  "deletedCount": 118,
  "error": null
}
DELETE/api/{namespace}/{type}Bearer InventDB SOAR + InventDB Serverless

Drop an entire type (its data directory, segments, indexes, and attachments) from disk. This is destructive and irreversible: unlike a soft-deleted row you cannot point-in-time-query it back. Admin / superadmin only. Use deleteAll instead if you only want to empty the rows but keep the type and its schema.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. inspections

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/api/pms/inspections' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "ok": true,
    "message": "Type pms.inspections deleted successfully"
  }
}
DELETE/api/namespaces/{namespace}Bearer InventDB SOAR + InventDB Serverless

Drop an entire namespace and every type inside it; the whole namespace folder is removed. This is the most destructive operation in the API, equivalent to dropping every type sequentially. Admin / superadmin only. There is no undo and no point-in-time recovery once the folder is gone.

Path & query parameters

NameInTypeDescription
namespacepathstringThe namespace to drop, e.g. pms_archive_2024.

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/api/namespaces/pms_archive_2024' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "ok": true,
    "message": "Namespace pms_archive_2024 deleted successfully"
  }
}

Vector embeddings

Universal vector embedding pipeline: MEANING() SQL is wired in transparently against per-segment HNSW indexes. Every user type with string properties is embedded automatically (MiniLM all-MiniLM-L6-v2, 384 dims); new writes are embedded inline and sealed segments are backfilled by a background worker. These admin-only endpoints expose install status, an aggregated per-segment overview for the Settings → Vector Indexes card, and a one-shot backfill trigger. You almost never call these to search; semantic search is done in SQL, e.g. WHERE MEANING('quiet condo near downtown') > 0.5.

GET/api/vectors/universal/statusBearer InventDB SOAR + InventDB Serverless

Returns whether the universal vector embedding pipeline is installed and loadable on this instance. Use it as a lightweight health probe before relying on MEANING() semantic search in SQL, or from the admin console to confirm the MiniLM model + tokenizer files were found at boot. Admin/superadmin only; a non-admin caller gets 403.

Path & query parameters

None. The status is instance-wide rather than scoped to a namespace or type.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/vectors/universal/status' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "embeddingGeneratorInstalled": true,
  "modelFilesExist": true,
  "note": "Universal vector embedding pipeline is active. New writes are auto-embedded; sealed segments are auto-backfilled in background."
}
GET/api/vectors/universal/overviewBearer InventDB SOAR + InventDB Serverless

Aggregates per-segment vector index counts across every user namespace.type (for example every table in the pms namespace) into a single round-trip for the Settings → Vector Indexes card. Cheap and lock-free: it walks each type's segments.json manifest from disk and tallies; it runs no DB queries. Use it to see how many sealed segments are still vectors_pending versus fully indexed. Admin/superadmin only.

Path & query parameters

None. Counts are aggregated across all user (non-_) namespaces and types.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/vectors/universal/overview' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "embeddingGeneratorInstalled": true,
  "modelName": "all-MiniLM-L6-v2",
  "modelDimensions": 384,
  "typesTotal": 14,
  "activeSegments": 14,
  "sealedSegmentsTotal": 6,
  "sealedSegmentsIndexed": 5,
  "sealedSegmentsPending": 1,
  "note": "Pipeline active. 1 sealed segment marked vectors_pending across pms.transactions; background worker will index it shortly."
}

Here typesTotal: 14 reflects the 14 pms tables (properties, owners, leases, tenants, transactions, work_orders, vendors, inspections, compliance, daily_tasks, …). Each type has exactly one active write segment, so activeSegments equals the number of types with data on disk. sealedSegmentsPending counts sealed segments the background backfill worker still needs to embed.

POST/api/vectors/universal/backfillBearer InventDB SOAR + InventDB Serverless

Triggers a one-shot, synchronous backfill across ALL types/segments tagged vectors_pending. A background thread already does this continuously, but this endpoint forces an immediate run from the admin console, useful right after a bulk import into pms.transactions or pms.leases when you want semantic search over the new rows without waiting for the worker. Returns the number of segments processed and wall-clock time. Admin/superadmin only; internal failure returns 500.

Path & query parameters

None. There is no request body; the backfill scope is every pending segment across all user types.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/vectors/universal/backfill' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "processedSegments": 1,
  "elapsedMs": 3
}

When nothing is pending (all sealed segments already indexed), the response is { "processedSegments": 0, "elapsedMs": 3 } and the call is effectively a no-op.

Indexes (admin)

Property B-tree index management: inspect per-property index statistics, run full rebuilds (Server-Sent Events stream), poll rebuild progress, consolidate (flush + fsync) indexes to disk, and pause/resume property indexing globally during bulk loads. Every endpoint is admin-only; the index files live on disk under <data_root>/<ns>/<type>/prop_*.tree.

GET/api/indexesBearer InventDB SOAR + InventDB Serverless

Returns a status grid for every user-namespace type, with per-property B-tree statistics (tree height, leaf count, distinct values, estimated cardinality). Use it to power an admin index-health dashboard or to decide which types need a rebuild. Admin-only; internal _* namespaces are skipped.

Response 200

curl -s 'https://acme.sandbox.inventdb.com/api/indexes' \
  -H 'Authorization: Bearer $TOKEN'
{
  "ok": true,
  "indexes": [
    {
      "namespace": "pms",
      "type": "properties",
      "recordCount": 500,
      "indexStatus": "active",
      "lastRebuilt": "2026-07-13T11:08:20.504Z",
      "propertyIndexes": [
        {
          "propertyName": "property_id",
          "totalEntries": 500,
          "treeHeight": 3,
          "leafCount": 12,
          "distinctValues": 500,
          "estimatedCardinality": 500
        },
        {
          "propertyName": "status",
          "totalEntries": 500,
          "treeHeight": 2,
          "leafCount": 2,
          "distinctValues": 4,
          "estimatedCardinality": 200
        },
        {
          "propertyName": "market_rent",
          "totalEntries": 500,
          "treeHeight": 3,
          "leafCount": 10,
          "distinctValues": 318,
          "estimatedCardinality": 200
        }
      ]
    },
    {
      "namespace": "pms",
      "type": "leases",
      "recordCount": 46,
      "indexStatus": "active",
      "lastRebuilt": null,
      "propertyIndexes": [
        {
          "propertyName": "status",
          "totalEntries": 46,
          "treeHeight": 2,
          "leafCount": 2,
          "distinctValues": 3,
          "estimatedCardinality": 18
        }
      ]
    }
  ]
}

indexStatus is active under normal operation; it reads rebuilding while a rebuild is in flight, or stale when property indexes lag the latest writes (usually self-heals on the next checkpoint).

GET/api/indexes/{namespace}/{type}/progressBearer InventDB SOAR + InventDB Serverless

Polling alternative to the SSE rebuild stream; returns a single snapshot of rebuild progress for one type. status is one of idle, rebuilding, completed, or failed. Poll this from tooling that can't (or shouldn't) hold an EventSource connection open while a rebuild runs.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. properties

Response 200

curl -s 'https://acme.sandbox.inventdb.com/api/indexes/pms/properties/progress' \
  -H 'Authorization: Bearer $TOKEN'
{
  "ok": true,
  "namespace": "pms",
  "type": "properties",
  "status": "idle",
  "progressPercent": null,
  "recordsProcessed": null,
  "totalRecords": null
}

While a rebuild is active the same shape carries live numbers, e.g. "status": "rebuilding", "progressPercent": 42.0, "recordsProcessed": 210, "totalRecords": 500.

GET/api/indexes/{namespace}/{type}/rebuildBearer InventDB SOAR + InventDB Serverless

Runs a full index rebuild for one type: it walks every row, drops the existing prop_*.tree files, and re-indexes from scratch. The response is a Server-Sent Events stream of progress frames; the terminal data: frame has status: completed or failed, followed by an event: done frame. Because EventSource can't send an Authorization header, pass the JWT via the ?token= query parameter instead.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. leases
tokenquerystringJWT bearer token (EventSource has no header support).

Request

curl -sN 'https://acme.sandbox.inventdb.com/api/indexes/pms/leases/rebuild?token=$TOKEN'

Response 200 text/event-stream

data: {"namespace":"pms","type":"leases","status":"rebuilding","totalRecords":0,"processedRecords":0,"progressPercentage":0.0,"startTime":"2026-07-13T11:08:16.337Z","elapsedMs":0}

data: {"namespace":"pms","type":"leases","status":"rebuilding","totalRecords":46,"processedRecords":0,"progressPercentage":0.0,"startTime":"2026-07-13T11:08:16.337Z","elapsedMs":0}

data: {"namespace":"pms","type":"leases","status":"completed","totalRecords":46,"processedRecords":46,"progressPercentage":100.0,"startTime":"2026-07-13T11:08:16.337Z","endTime":"2026-07-13T11:08:20.504Z","elapsedMs":4167}

event: done
data: {}
POST/api/{namespace}/{type}/reindexBearer InventDB SOAR + InventDB Serverless

POST variant of the SSE rebuild: same outcome as GET /api/indexes/:ns/:type/rebuild, but addressable via POST so tooling that can't open an EventSource connection can still kick off a rebuild. It runs as the system principal and streams the same progress frames back. Prefer this from scripts and background jobs; prefer the GET/SSE form for browser-driven admin consoles.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. leases

Request

curl -sN -X POST 'https://acme.sandbox.inventdb.com/api/pms/leases/reindex' \
  -H 'Authorization: Bearer $TOKEN'

Response 200 text/event-stream

data: {"namespace":"pms","type":"leases","status":"rebuilding","totalRecords":46,"processedRecords":0,"progressPercentage":0.0,"startTime":"2026-07-13T11:08:20.677Z","elapsedMs":0}

data: {"namespace":"pms","type":"leases","status":"completed","totalRecords":46,"processedRecords":46,"progressPercentage":100.0,"startTime":"2026-07-13T11:08:20.677Z","endTime":"2026-07-13T11:08:23.689Z","elapsedMs":3011}

event: done
data: {}
POST/api/indexes/{namespace}/{type}/consolidateBearer InventDB SOAR + InventDB Serverless

Flushes and fsyncs the indexes for one type to disk. Synchronous: the call returns only once every dirty page is durable. Use it before snapshotting the data directory or before a graceful shutdown so no index writes are lost.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. properties

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/indexes/pms/properties/consolidate' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "message": "Index consolidation completed for pms.properties - all data synced to disk"
}
POST/api/indexes/consolidate-allBearer InventDB SOAR + InventDB Serverless

Flushes and fsyncs every user-namespace type to disk in one call; it walks list_namespaces() → list_types(), skipping internal _* stores, and syncs each. Returns 200 when all types sync cleanly, or 206 if some fail, with a message that summarizes the count.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/indexes/consolidate-all' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "message": "Consolidated 20 type stores - all data synced to disk"
}

Response 206

{
  "ok": false,
  "message": "Consolidated 18 of 20 type stores - 2 failed to sync",
  "error": "pms.transactions: disk I/O error"
}
DELETE/api/indexes/{namespace}/{type}Bearer InventDB SOAR + InventDB Serverless

Disabled by design: this endpoint is a no-op. It returns 200 with an explanatory message instead of deleting anything. The Rust engine rebuilds indexes incrementally from the source data, so an API-driven delete would leave a window of empty indexes with no safety net. To refresh indexes, use GET /api/indexes/:ns/:type/rebuild (or the POST /reindex variant) instead.

Path & query parameters

NameInTypeDescription
namespacepathstringe.g. pms
typepathstringe.g. properties

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/api/indexes/pms/properties' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "message": "Index deletion not recommended. Type pms.properties has 29 property indexes. Use /rebuild endpoint to refresh indexes instead."
}
POST/api/indexingBearer InventDB SOAR + InventDB Serverless

Globally enables or disables property indexing. When disabled, every write skips index maintenance, the standard trick for speeding up large bulk loads. Afterwards, re-enable with this endpoint and then call POST /api/indexes/:ns/:type/consolidate (or run a full rebuild) so the indexes catch up with the loaded rows.

Path & query parameters

NameInTypeDescription
enabledquerybooleantrue to re-enable indexing, false to pause it. Required.

Request

# pause indexing before a bulk load
curl -s -X POST 'https://acme.sandbox.inventdb.com/api/indexing?enabled=false' \
  -H 'Authorization: Bearer $TOKEN'

# ...bulk-load pms.properties, then re-enable...
curl -s -X POST 'https://acme.sandbox.inventdb.com/api/indexing?enabled=true' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "indexingEnabled": true
}
POST/api/indexing/waitBearer InventDB SOAR + InventDB Serverless

Blocks until all pending index work has drained, then returns 200. Handy in scripted workflows that bulk-load a type (e.g. pms.transactions) and then immediately query it by an indexed column; call this first to guarantee the indexes are caught up before the read.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/indexing/wait' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "message": "All indexing operations completed"
}
POST/api/indexes/clear-historyBearer InventDB SOAR + InventDB Serverless

Clears the rebuild-progress history table, resetting the "Last rebuild" timeline shown in the admin console after maintenance. This is bookkeeping only; it has no effect on live data or on the indexes themselves.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/indexes/clear-history' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "message": "Rebuild history cleared"
}

Relationships

The foreign-key graph for a namespace. Each edge is a property-to-property link between two types (e.g. pms.leases.property_id → pms.properties.property_id) carrying a cardinality hint and an enforce flag for referential integrity. Read the graph, replace it (admin only), drop it, auto-detect candidate edges from column-name heuristics plus data sampling, or fetch the per-type property schema that drives the Console's relationship designer.

GET/api/relationships/{namespace}Bearer InventDB SOAR + InventDB Serverless

Returns the full relationship graph for a namespace as a single graph record. Each entry in edges links a source property to a target property, with a cardinality hint (many-to-one, one-to-many, …), an enforce flag for referential integrity, and an optional human label. Use it to render join diagrams or to seed an edit before a PUT. An empty graph (edges: []) is returned when nothing is configured yet.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace whose graph to read, e.g. pms.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/relationships/pms' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "_id": "graph",
  "edges": [
    {
      "sourceType": "leases",
      "sourceProperty": "property_id",
      "targetType": "properties",
      "targetProperty": "property_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "label": "belongs to"
    },
    {
      "sourceType": "leases",
      "sourceProperty": "tenant_id",
      "targetType": "tenants",
      "targetProperty": "tenant_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "label": "belongs to"
    },
    {
      "sourceType": "properties",
      "sourceProperty": "owner_id",
      "targetType": "owners",
      "targetProperty": "owner_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "label": "belongs to"
    }
  ],
  "layout": { "ai_detected_at": "2026-07-11T12:46:55.161Z" },
  "_createdAt": "2026-07-11T12:46:55.185234300+00:00",
  "_updatedAt": "2026-07-11T12:46:55.185234300+00:00",
  "_createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee"
}
PUT/api/relationships/{namespace}Bearer InventDB SOAR + InventDB Serverless

Replaces the relationship graph for a namespace. Admin / superadmin only: the graph defines the data model itself (which types reference which, and on what columns), so a plain write grant is not enough. PUT semantics are wholesale: the supplied edges array fully replaces what was there, so GET first, edit the array, then send it back. The optional layout blob (node positions, zoom) is round-tripped verbatim.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace whose graph to overwrite, e.g. pms.

Request

curl -s -X PUT 'https://acme.sandbox.inventdb.com/api/relationships/pms' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{
    "edges": [
      {
        "sourceType": "leases",
        "sourceProperty": "property_id",
        "targetType": "properties",
        "targetProperty": "property_id",
        "cardinality": "many-to-one",
        "enforce": false,
        "label": "belongs to"
      },
      {
        "sourceType": "leases",
        "sourceProperty": "tenant_id",
        "targetType": "tenants",
        "targetProperty": "tenant_id",
        "cardinality": "many-to-one",
        "enforce": false,
        "label": "belongs to"
      }
    ],
    "layout": { "ai_detected_at": "2026-07-11T12:46:55.161Z" }
  }'
{
  "edges": [
    {
      "sourceType": "leases",
      "sourceProperty": "property_id",
      "targetType": "properties",
      "targetProperty": "property_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "label": "belongs to"
    },
    {
      "sourceType": "leases",
      "sourceProperty": "tenant_id",
      "targetType": "tenants",
      "targetProperty": "tenant_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "label": "belongs to"
    }
  ],
  "layout": { "ai_detected_at": "2026-07-11T12:46:55.161Z" }
}

Response 200

{ "ok": true }
DELETE/api/relationships/{namespace}Bearer InventDB SOAR + InventDB Serverless

Drops the relationship graph for a namespace. Requires admin permission on the namespace. The underlying edge records and the saved layout are removed, but the namespace's actual data (properties, leases, tenants, …) is left completely untouched; only the FK metadata goes away.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace whose graph to delete, e.g. pms.

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/api/relationships/pms' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{ "ok": true }
POST/api/relationships/{namespace}/detectBearer InventDB SOAR + InventDB Serverless

Auto-detects candidate relationships by combining column-name heuristics (*_id columns map to a matching type), data sampling (checks that the foreign-key values actually resolve in the target type), and an optional LLM assist for naming-mismatch links. Pass an optional types array to restrict the scan; omit it to scan every type in the namespace. The response is suggestions only; nothing is persisted. The user reviews them in the designer and PUTs the final set back via save_relationships.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace to scan, e.g. pms.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/relationships/pms/detect' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "types": ["leases", "tenants", "properties", "owners"] }'
{ "types": ["leases", "tenants", "properties", "owners"] }

Response 200

{
  "suggestions": [
    {
      "sourceType": "leases",
      "sourceProperty": "property_id",
      "targetType": "properties",
      "targetProperty": "_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "confidence": 0.4,
      "method": "heuristic",
      "reason": "Property 'property_id' matches type 'properties'",
      "dataMatchRatio": 0.0,
      "sampleSize": 5,
      "matchCount": 0
    },
    {
      "sourceType": "leases",
      "sourceProperty": "tenant_id",
      "targetType": "tenants",
      "targetProperty": "_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "confidence": 0.4,
      "method": "heuristic",
      "reason": "Property 'tenant_id' matches type 'tenants'",
      "dataMatchRatio": 0.0,
      "sampleSize": 5,
      "matchCount": 0
    },
    {
      "sourceType": "properties",
      "sourceProperty": "owner_id",
      "targetType": "owners",
      "targetProperty": "_id",
      "cardinality": "many-to-one",
      "enforce": false,
      "confidence": 0.4,
      "method": "heuristic",
      "reason": "Property 'owner_id' matches type 'owners'",
      "dataMatchRatio": 0.0,
      "sampleSize": 5,
      "matchCount": 0
    }
  ],
  "typesAnalyzed": 14
}
GET/api/relationships/{namespace}/types-schemaBearer InventDB SOAR + InventDB Serverless

Returns every non-system type in a namespace together with its recordCount and known properties[]. This drives the InventDB Console's relationship designer so the user can pick valid source and target columns (e.g. leases.property_idproperties.property_id). Property lists reflect what the schemaless engine has seen so far, so newer columns appear automatically as rows are written.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace whose type schema to list, e.g. pms.

Request

curl -s -X GET 'https://acme.sandbox.inventdb.com/api/relationships/pms/types-schema' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "types": [
    {
      "name": "leases",
      "recordCount": 46,
      "properties": [
        { "name": "_id" },
        { "name": "lease_id" },
        { "name": "property_id" },
        { "name": "tenant_id" },
        { "name": "tenant_name" },
        { "name": "market_rent" },
        { "name": "contract_rent" },
        { "name": "deposit_held" },
        { "name": "lease_start" },
        { "name": "lease_end" },
        { "name": "status" }
      ]
    },
    {
      "name": "properties",
      "recordCount": 50,
      "properties": [
        { "name": "_id" },
        { "name": "property_id" },
        { "name": "owner_id" },
        { "name": "street" },
        { "name": "city" },
        { "name": "state" },
        { "name": "market_rent" },
        { "name": "market_value" },
        { "name": "status" }
      ]
    },
    {
      "name": "owners",
      "recordCount": 10,
      "properties": [
        { "name": "_id" },
        { "name": "owner_id" },
        { "name": "name" },
        { "name": "type" },
        { "name": "phone" },
        { "name": "email" },
        { "name": "mgmt_fee" },
        { "name": "payout_method" }
      ]
    },
    {
      "name": "transactions",
      "recordCount": 2261,
      "properties": [
        { "name": "_id" },
        { "name": "date" },
        { "name": "type" },
        { "name": "property_id" },
        { "name": "owner_id" },
        { "name": "account_category" },
        { "name": "amount" },
        { "name": "method" }
      ]
    }
  ]
}

Workflows

Event-driven workflows engine: workflow CRUD, the runs timeline, manual fire, run export, and lifecycle toggles (pause / resume / activate). A workflow pairs a trigger (cron, gmail, webhook, manual, …) with an ordered plan of steps (sql_query, render_report, llm_extract, send_email, notify_user, create_calendar_event, finish, …). Every workflow is owned by the caller and scoped by RLS. All endpoints require a Bearer token.

POST/api/workflowsBearer InventDB SOAR + InventDB Serverless

Create a new event-driven workflow owned by the caller. The body carries the display name, the trigger_kind + kind-specific trigger_spec, a short natural-language trigger_intent, and the ordered plan array of steps. New workflows land in pending_approval=true / active=false; call /activate (or the pause/resume toggles) to let the dispatcher fire them. sandbox defaults to true, so a fresh workflow never produces externally-visible side effects until you deliberately turn sandbox off.

Request body fields

NameInTypeDescription
namebodystringDisplay name (non-empty).
trigger_kindbodystringcron | gmail | webhook | manual | …
trigger_specbodyobjectKind-specific config, e.g. { "expr": "0 9 * * *", "tz": "Asia/Calcutta" } for cron.
trigger_intentbodystringShort natural-language description of what should happen.
planbodyarrayOrdered WorkflowStep objects. Each needs idx, label, narration, kind and kind-specific fields (a notify_user step needs title + body; a sql_query step needs sql).
tools_allowedbodyarrayAllowlist for LLM tool calls inside the run (empty = none).
budgetbodyobjectOptional ceiling, e.g. { "max_tool_calls": 25, "max_input_tokens": 50000 }.
sandboxbodybooleanDefaults true; when true the run makes no externally-visible side effects (emails go to inbox staging only).

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/workflows' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{
  "name": "Overdue Rent Alert",
  "trigger_kind": "cron",
  "trigger_spec": { "expr": "0 8 * * *", "tz": "Asia/Calcutta" },
  "trigger_intent": "Every morning, find rent transactions and notify the operator.",
  "plan": [
    { "idx": 1, "label": "Find rent activity",
      "narration": "Pulls recent rent income rows for the portfolio.",
      "kind": "sql_query", "save_as": "rent",
      "sql": "SELECT property_id, amount FROM pms.transactions WHERE type = '\''Income'\'' AND account_category = '\''Rent'\'' LIMIT 50" },
    { "idx": 2, "label": "Notify operator",
      "narration": "Posts an in-app notification with the rent summary.",
      "kind": "notify_user", "title": "Rent summary ready",
      "body": "Rent activity summary is ready to review." }
  ],
  "tools_allowed": [],
  "sandbox": true
}'
{
  "name": "Overdue Rent Alert",
  "trigger_kind": "cron",
  "trigger_spec": { "expr": "0 8 * * *", "tz": "Asia/Calcutta" },
  "trigger_intent": "Every morning, find rent transactions and notify the operator.",
  "plan": [
    {
      "idx": 1,
      "label": "Find rent activity",
      "narration": "Pulls recent rent income rows for the portfolio.",
      "kind": "sql_query",
      "save_as": "rent",
      "sql": "SELECT property_id, amount FROM pms.transactions WHERE type = 'Income' AND account_category = 'Rent' LIMIT 50"
    },
    {
      "idx": 2,
      "label": "Notify operator",
      "narration": "Posts an in-app notification with the rent summary.",
      "kind": "notify_user",
      "title": "Rent summary ready",
      "body": "Rent activity summary is ready to review."
    }
  ],
  "tools_allowed": [],
  "sandbox": true
}

Response 200

{
  "ok": true,
  "data": {
    "_id": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2",
    "name": "Overdue Rent Alert",
    "owner_user_id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "trigger_kind": "cron",
    "trigger_spec": { "expr": "0 8 * * *", "tz": "Asia/Calcutta" },
    "trigger_intent": "Every morning, find rent transactions and notify the operator.",
    "plan": [
      { "idx": 1, "label": "Find rent activity", "narration": "Pulls recent rent income rows for the portfolio.", "kind": "sql_query", "save_as": "rent", "sql": "SELECT property_id, amount FROM pms.transactions WHERE type = 'Income' AND account_category = 'Rent' LIMIT 50" },
      { "idx": 2, "label": "Notify operator", "narration": "Posts an in-app notification with the rent summary.", "kind": "notify_user", "title": "Rent summary ready", "body": "Rent activity summary is ready to review." }
    ],
    "pending_approval": true,
    "tools_allowed": [],
    "budget": { "max_tool_calls": 25, "max_input_tokens": 50000, "max_runtime_secs": 1800, "max_parked_secs": 1209600 },
    "sandbox": true,
    "active": false,
    "version": 1,
    "created_at": "2026-07-13T11:12:24.314637900+00:00",
    "updated_at": "2026-07-13T11:12:24.314637900+00:00",
    "next_run_at": "2026-07-14T02:30:00+00:00",
    "_createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee"
  }
}
GET/api/workflowsBearer InventDB SOAR + InventDB Serverless

List every workflow owned by the caller, newest activity first. Each row includes the trigger kind, natural-language intent, lifecycle flags (active, pending_approval), and dispatch timestamps. Pass ?include_deleted=true to also surface soft-deleted rows for audit or undo flows.

Path & query parameters

NameInTypeDescription
include_deletedquerybooleanInclude soft-deleted workflows (default false).

Request

curl -s 'https://acme.sandbox.inventdb.com/api/workflows?include_deleted=false' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "workflows": [
      {
        "_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
        "name": "Daily Owner Statements: 9 AM email to each owner",
        "trigger_kind": "cron",
        "trigger_intent": "Every morning at 9:00 AM (Asia/Calcutta), render the \"Owner Statement\" report for each owner and email each owner their own statement.",
        "active": true,
        "pending_approval": false,
        "deleted_at": null,
        "created_at": "2026-07-11T12:40:47.156715100+00:00",
        "updated_at": "2026-07-13T03:30:18.793315+00:00",
        "last_dispatched_at": "2026-07-13T03:30:18.793281500+00:00"
      },
      {
        "_id": "1d315f38-db91-4daf-8055-02b56b92de14",
        "name": "Lease Renewal Reminder (60-day, with approval)",
        "trigger_kind": "cron",
        "trigger_intent": "Every morning, find active leases expiring within 60 days, draft a renewal-reminder email per tenant, and park for approval.",
        "active": false,
        "pending_approval": true,
        "deleted_at": null,
        "created_at": "2026-07-09T15:17:35.036894400+00:00",
        "updated_at": "2026-07-09T15:17:35.036894400+00:00",
        "last_dispatched_at": null
      }
    ]
  }
}
GET/api/workflows/{id}Bearer InventDB SOAR + InventDB Serverless

Read a single workflow's full definition: the complete plan, trigger_spec, budget, lifecycle flags, and the natural-language message_context that built it. Use this to inspect or clone a workflow before editing it with PUT.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).

Request

curl -s 'https://acme.sandbox.inventdb.com/api/workflows/138a46dd-6ba9-4505-8ae5-ba9b4c4d09d1' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "_id": "138a46dd-6ba9-4505-8ae5-ba9b4c4d09d1",
    "name": "Daily Owner Portfolio Summary email",
    "owner_user_id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "trigger_kind": "cron",
    "trigger_spec": { "expr": "0 9 * * *", "tz": "Asia/Calcutta" },
    "trigger_intent": "Every morning at 9:00 AM (Asia/Calcutta), render the \"Owner Portfolio Summary\" report for owner O-1002 and email it.",
    "plan": [
      { "idx": 1, "label": "Render Owner Portfolio Summary", "narration": "Renders the saved report template live against pms data for owner O-1002.", "save_as": "report", "kind": "render_report", "template_id": "tpl_095c0b7a07a2", "params": { "owner_id": "O-1002" } },
      { "idx": 2, "label": "Email the report to me", "narration": "Emails the rendered portfolio summary HTML as the morning digest.", "kind": "send_email", "to": "maya@pmco.example", "subject": "Owner Portfolio Summary ${today}", "body": "${report.html}" },
      { "idx": 3, "label": "Done", "narration": "Marks the run complete once the email has been dispatched.", "kind": "finish", "summary": "Rendered the report and emailed it." }
    ],
    "pending_approval": false,
    "tools_allowed": [],
    "budget": { "max_tool_calls": 25, "max_input_tokens": 50000, "max_runtime_secs": 1800, "max_parked_secs": 1209600 },
    "sandbox": false,
    "active": true,
    "version": 1,
    "created_at": "2026-07-11T06:35:02.843768600+00:00",
    "updated_at": "2026-07-13T03:30:18.681641700+00:00",
    "last_dispatched_at": "2026-07-13T03:30:18.664288600+00:00",
    "next_run_at": "2026-07-14T03:30:00+00:00",
    "_createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee"
  }
}
PUT/api/workflows/{id}Bearer InventDB SOAR + InventDB Serverless

Update a workflow's metadata or plan. All fields are optional; send only what changed. When plan is included it is re-validated and any errors come back as 400 with an issues array describing each problem; non-plan edits (like a rename) skip validation to avoid unnecessary SQL probes. Each successful update bumps version.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).
namebodystringOptional new display name.
trigger_specbodyobjectOptional new trigger config.
planbodyarrayOptional new step plan (re-validated).

Request

curl -s -X PUT 'https://acme.sandbox.inventdb.com/api/workflows/1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "name": "Overdue Rent Alert (renamed)" }'
{ "name": "Overdue Rent Alert (renamed)" }

Response 200

{
  "ok": true,
  "data": {
    "_id": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2",
    "name": "Overdue Rent Alert (renamed)",
    "owner_user_id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "trigger_kind": "cron",
    "trigger_spec": { "expr": "0 8 * * *", "tz": "Asia/Calcutta" },
    "trigger_intent": "Every morning, find rent transactions and notify the operator.",
    "pending_approval": false,
    "sandbox": true,
    "active": true,
    "version": 2,
    "updated_at": "2026-07-13T11:13:02.128849400+00:00"
  }
}
DELETE/api/workflows/{id}Bearer InventDB SOAR + InventDB Serverless

Soft-delete a workflow. Past runs and their event history are preserved (they still show up under /runs and export), and the row can be surfaced again with ?include_deleted=true, but the workflow stops firing on new events. Use this instead of a hard delete so audit trails survive.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/api/workflows/1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": { "deleted": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2" }
}
POST/api/workflows/{id}/activateBearer InventDB SOAR + InventDB Serverless

Activate a workflow: clears pending_approval and sets active=true. Idempotent for already-active rows. The optional body lets the AI canvas persist a sandbox value in the same click that approves the workflow; omit the body entirely to leave the persisted sandbox flag untouched.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).
sandboxbodyboolean | nullOptional. When set, overrides the persisted sandbox flag before flipping active on. Omit to preserve the current value.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/workflows/1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2/activate' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "sandbox": true }'
{ "sandbox": true }

Response 200

{
  "ok": true,
  "data": {
    "_id": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2",
    "name": "Overdue Rent Alert",
    "trigger_kind": "cron",
    "trigger_spec": { "expr": "0 8 * * *", "tz": "Asia/Calcutta" },
    "pending_approval": false,
    "active": true,
    "sandbox": true,
    "version": 1,
    "updated_at": "2026-07-13T11:12:41.902117300+00:00"
  }
}
POST/api/workflows/{id}/pauseBearer InventDB SOAR + InventDB Serverless

Pause a workflow: sets active=false. New trigger events queue but are not dispatched until you resume; any in-flight runs keep going to completion. No request body.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/workflows/1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2/pause' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "_id": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2",
    "name": "Overdue Rent Alert",
    "active": false,
    "pending_approval": false,
    "version": 1,
    "updated_at": "2026-07-13T11:12:49.551030700+00:00"
  }
}
POST/api/workflows/{id}/resumeBearer InventDB SOAR + InventDB Serverless

Resume a paused workflow: sets active=true again. Backlog events that arrived while it was paused are not replayed; they are marked with an ignored_reason. Only events that fire after resume will dispatch. No request body.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/workflows/1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2/resume' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "_id": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2",
    "name": "Overdue Rent Alert",
    "active": true,
    "pending_approval": false,
    "version": 1,
    "updated_at": "2026-07-13T11:12:56.207744100+00:00"
  }
}
POST/api/workflows/{id}/runBearer InventDB SOAR + InventDB Serverless

Manually fire a workflow once. This appends a synthetic manual event carrying the supplied sample_payload; the router picks it up on its next tick and creates a run. Set sandbox_override: true to force this single run to be non-side-effecting even if the workflow itself has sandbox off; this is exactly how the canvas "Test" button works.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).
sample_payloadbodyobjectOptional payload injected as the synthetic manual event's data.
sandbox_overridebodybooleanOptional. Force this run to be non-side-effecting regardless of the workflow's own sandbox flag.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/workflows/1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2/run' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "sample_payload": { "owner_id": "O-1002" }, "sandbox_override": true }'
{
  "sample_payload": { "owner_id": "O-1002" },
  "sandbox_override": true
}

Response 200

{
  "ok": true,
  "data": {
    "event_id": "cc5097c6-269c-4b59-8f9d-3766f8e3d2d8",
    "workflow_id": "1f34ff78-55a0-40a8-9a55-3fb62d7e4bd2",
    "status": "pending",
    "note": "Event queued. Dispatch happens once the router picks it up on its next tick."
  }
}
GET/api/workflows/{id}/runsBearer InventDB SOAR + InventDB Serverless

List all runs of one workflow, newest first. Each entry is a run summary (status, start/end timestamps, sandbox flag, and any top-level error) without the per-step timeline. Fetch a single run's /runs/{id} to drill into its steps.

Path & query parameters

NameInTypeDescription
idpathstringWorkflow _id (uuid).

Request

curl -s 'https://acme.sandbox.inventdb.com/api/workflows/3d754a89-3b42-462f-969e-80b7ece7d880/runs' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "runs": [
      {
        "_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
        "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
        "status": "succeeded",
        "started_at": "2026-07-13T03:30:19.370381+00:00",
        "ended_at": "2026-07-13T03:30:26.709449900+00:00",
        "last_active_at": "2026-07-13T03:30:26.709449900+00:00",
        "error": null,
        "sandbox": false,
        "trigger_event_id": "d3c83317-8bda-4ec8-bc6d-1856325941ad"
      }
    ]
  }
}
GET/api/workflows/runsBearer InventDB SOAR + InventDB Serverless

List every run across all workflows the caller owns, newest first. This is the global runs feed that powers the "recent activity" dashboard: one flat array of run summaries spanning all of your workflows.

Request

curl -s 'https://acme.sandbox.inventdb.com/api/workflows/runs' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "runs": [
      {
        "_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
        "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
        "status": "succeeded",
        "started_at": "2026-07-13T03:30:19.370381+00:00",
        "ended_at": "2026-07-13T03:30:26.709449900+00:00",
        "last_active_at": "2026-07-13T03:30:26.709449900+00:00",
        "error": null,
        "sandbox": false
      },
      {
        "_id": "a1f0c2d4-7e6b-4c33-9a12-55b0e9f4d201",
        "workflow_id": "138a46dd-6ba9-4505-8ae5-ba9b4c4d09d1",
        "status": "succeeded",
        "started_at": "2026-07-13T03:30:18.700000+00:00",
        "ended_at": "2026-07-13T03:30:23.410000+00:00",
        "last_active_at": "2026-07-13T03:30:23.410000+00:00",
        "error": null,
        "sandbox": false
      }
    ]
  }
}
GET/api/workflows/runs/{id}Bearer InventDB SOAR + InventDB Serverless

Read a single run together with its full step timeline. Returns { run, steps } where steps is ordered by idx and includes each step's role (model_in, tool_call, tool_result), the tool_name, its tool_args, the tool_result, and timing. Use it to inspect exactly what a workflow did on a given run.

Path & query parameters

NameInTypeDescription
idpathstringRun _id (uuid).

Request

curl -s 'https://acme.sandbox.inventdb.com/api/workflows/runs/3330bdf7-8891-466b-85b4-de5fa588140e' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "run": {
      "_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
      "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
      "trigger_event_id": "d3c83317-8bda-4ec8-bc6d-1856325941ad",
      "principal": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "status": "succeeded",
      "started_at": "2026-07-13T03:30:19.370381+00:00",
      "ended_at": "2026-07-13T03:30:26.709449900+00:00",
      "tool_calls_used": 0,
      "input_tokens_used": 0,
      "output_tokens_used": 0,
      "cost_used_usd": 0.0,
      "sandbox": false,
      "depth": 0
    },
    "steps": [
      {
        "_id": "4de6ddae-3850-40dd-bc34-25a0beddd118",
        "run_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
        "idx": 0,
        "role": "model_in",
        "content": "Workflow 'Daily Owner Statements: 9 AM email to each owner' triggered.",
        "tool_name": null,
        "tool_args": null,
        "tool_result": null,
        "created_at": "2026-07-13T03:30:19.379397900+00:00"
      },
      {
        "_id": "82308f0c-b63d-44cf-9d62-3d94ee5220b4",
        "run_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
        "idx": 1,
        "role": "tool_call",
        "content": "[Fetch all owners] Looks up every owner so a statement can be emailed to each one.",
        "tool_name": "sql_query",
        "tool_args": { "kind": "sql_query", "sql": "SELECT owner_id, email, name FROM pms.owners" },
        "tool_result": null,
        "created_at": "2026-07-13T03:30:19.444932100+00:00"
      },
      {
        "_id": "2805b7d7-3546-427f-bf7c-ba6bf75bcdcd",
        "run_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
        "idx": 1,
        "role": "tool_result",
        "content": "",
        "tool_name": "sql_query",
        "tool_result": [
          { "owner_id": "O-1002", "email": "vikram.sundara75@team758135.testinator.email", "name": "Sundara Holdings LLC" },
          { "owner_id": "O-1004", "email": "robert.whitfield97@team758135.testinator.email", "name": "Robert & Susan Whitfield" }
        ],
        "created_at": "2026-07-13T03:30:19.494639400+00:00"
      }
    ]
  }
}
GET/api/workflows/runs/{id}/exportBearer InventDB SOAR + InventDB Serverless

Dump a run, its steps, the original trigger event, and any notifications it produced: a single self-contained bundle for audit, debugging, or support hand-off. The run embeds a workflow_snapshot (the exact plan as it ran), and the versioned schema field (e.g. inventdb.workflow.run.v1) is the stable contract for consumers of the export.

Path & query parameters

NameInTypeDescription
idpathstringRun _id (uuid).

Request

curl -s 'https://acme.sandbox.inventdb.com/api/workflows/runs/3330bdf7-8891-466b-85b4-de5fa588140e/export' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "schema": "inventdb.workflow.run.v1",
    "exported_at": "2026-07-13T11:10:59.610687100+00:00",
    "run": {
      "_id": "3330bdf7-8891-466b-85b4-de5fa588140e",
      "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
      "trigger_event_id": "d3c83317-8bda-4ec8-bc6d-1856325941ad",
      "principal": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "workflow_snapshot": {
        "_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
        "name": "Daily Owner Statements: 9 AM email to each owner",
        "trigger_kind": "cron",
        "trigger_spec": { "expr": "0 9 * * *", "tz": "Asia/Calcutta" },
        "plan": [
          { "idx": 1, "label": "Fetch all owners", "save_as": "owners", "kind": "sql_query", "sql": "SELECT owner_id, email, name FROM pms.owners" }
        ],
        "sandbox": false,
        "active": true,
        "version": 2
      },
      "status": "succeeded",
      "started_at": "2026-07-13T03:30:19.370381+00:00",
      "ended_at": "2026-07-13T03:30:26.709449900+00:00"
    },
    "steps": [
      {
        "_id": "4de6ddae-3850-40dd-bc34-25a0beddd118",
        "idx": 0,
        "role": "model_in",
        "content": "Workflow 'Daily Owner Statements: 9 AM email to each owner' triggered.",
        "created_at": "2026-07-13T03:30:19.379397900+00:00"
      }
    ],
    "trigger_event": {
      "_id": "d3c83317-8bda-4ec8-bc6d-1856325941ad",
      "source": "cron",
      "occurred_at": "2026-07-13T03:30:18.793281500+00:00",
      "payload": {
        "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880",
        "fired_at": "2026-07-13T03:30:18.793281500+00:00",
        "trigger_intent": "Every morning at 9:00 AM (Asia/Calcutta), render the \"Owner Statement\" report for each owner and email it."
      },
      "correlation_keys": { "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880" },
      "status": "dispatched",
      "dispatched_to": [
        { "workflow_id": "3d754a89-3b42-462f-969e-80b7ece7d880", "run_id": "3330bdf7-8891-466b-85b4-de5fa588140e" }
      ],
      "dispatched_at": "2026-07-13T03:30:19.375385200+00:00",
      "user_id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee"
    },
    "notifications": []
  }
}

Outbound webhooks (change signals)

Push notifications for data changes: subscribe an HTTPS endpoint to one (namespace, type) scope and InventDB POSTs a signed, data-free signal whenever rows change; your receiver verifies the HMAC signature, then pulls fresh rows back over /sql, /db, or the /api/changes cursor feed. No row data ever leaves in the webhook itself. Deliveries are debounced per subscription, retried with exponential backoff on failure, and the subscription degrades (then goes dead) if your endpoint keeps failing. Available on both products; all endpoints require a Bearer token.

POST/api/webhooks/subscriptionsBearer InventDB SOAR + InventDB Serverless

Create a subscription. The response carries the subscription and the signing secret; the secret is returned once and never again; store it in your receiver. Every delivery is signed X-InventDB-Signature: sha256=HMAC_SHA256(secret, "<timestamp>.<body>") with X-InventDB-Timestamp and X-InventDB-Delivery-Id headers.

Request body fields

NameInTypeDescription
target_urlbodystringYour HTTPS receiver endpoint.
namespace / typebodystringThe single table scope the subscription watches.
opsbodyarraySubset of ["insert","update","delete"].
debounce_msbodynumberCoalesce window; at most one delivery per window per subscription.

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/api/webhooks/subscriptions' \
  -H 'Authorization: Bearer $TOKEN' -H 'content-type: application/json' \
  -d '{ "target_url": "https://ops.example.com/hooks/inventdb",
        "namespace": "pms", "type": "maintenance_requests",
        "ops": ["insert","update"], "debounce_ms": 1000 }'

Response (secret shown ONCE)

{ "ok": true,
  "subscription": { "id": "webhook_sub:5482666c33ca435b9ed7757b447b0f2f",
    "target_url": "https://ops.example.com/hooks/inventdb",
    "namespace": "pms", "type": "maintenance_requests",
    "ops": ["insert","update"], "debounce_ms": 1000,
    "active": true, "dead": false, "degraded": false, "cursor": 5548 },
  "secret": "whsec_…store-me-now…" }

What your endpoint receives (data-free signal)

POST /hooks/inventdb
X-InventDB-Signature: sha256=hex(HMAC_SHA256(secret, "<timestamp>.<body>"))
X-InventDB-Timestamp: 1784999533
X-InventDB-Delivery-Id: 49965c89bc1543b6bc33f3ec95a37475

{ "subscription_id": "webhook_sub:5482666c33ca435b9ed7757b447b0f2f",
  "cursor": 5673,
  "scope": { "ns": "pms", "type": "maintenance_requests" },
  "hint": { "changed": 1, "ops": ["update"] },
  "test": false,
  "ts": "2026-07-25T17:32:12Z" }
GET/api/webhooks/subscriptionsBearer InventDB SOAR + InventDB Serverless

List the caller's subscriptions with scope, ops, debounce, active/dead/degraded flags and delivery cursor. Secrets are never returned.

GET/api/webhooks/subscriptions/{id}Bearer InventDB SOAR + InventDB Serverless

Fetch one subscription. The signing secret is never returned; only rotate-secret mints a new one.

PATCH/api/webhooks/subscriptions/{id}Bearer InventDB SOAR + InventDB Serverless

Edit target_url, ops, debounce_ms and/or active (set active:false to pause deliveries without losing the cursor). Changing the target does not rotate the secret.

DELETE/api/webhooks/subscriptions/{id}Bearer InventDB SOAR + InventDB Serverless

Remove the subscription; deliveries stop immediately.

GET/api/webhooks/subscriptions/{id}/statusBearer InventDB SOAR + InventDB Serverless

Delivery health: cursor (last delivered change LSN) vs head (current change-log LSN), plus attempts, failed_deliveries, last_delivery_at and the dead/degraded flags. cursor < head with failures means your receiver is erroring and retries are backing off.

{ "ok": true, "id": "webhook_sub:5482666c…", "cursor": 5680, "head": 5689,
  "active": true, "dead": false, "degraded": false,
  "attempts": 0, "failed_deliveries": 0,
  "last_delivery_at": "2026-07-25T17:32:13Z" }
POST/api/webhooks/subscriptions/{id}/rotate-secretBearer InventDB SOAR + InventDB Serverless

Mint a new HMAC signing secret (returned once). Deploy it to your receiver before rotating, or verify against both secrets during switchover.

POST/api/webhooks/subscriptions/{id}/testBearer InventDB SOAR + InventDB Serverless

Fire one signed delivery immediately with "test": true in the body; verify the signature and return 200 without acting. Returns the transport error when the target is unreachable.

GET/api/changesBearer InventDB SOAR + InventDB Serverless

The pull side of eventing: cursor-based read of the change log for one scope. ?namespace=pms&type=maintenance_requests&since=<cursor>&limit=100 returns changed record ids (with op + LSN) and nextCursor. Ids are RLS-filtered: a caller only sees ids for rows their row filters permit. Webhook signals tell you when to read; this feed (or /db, /sql) is where you read.

GUIDEWorking example: property-management auto-dispatch (Python)

A complete, tested flow: a tenant files an urgent maintenance request in InventDB; the webhook signal hits a small Python service (stdlib only, no frameworks) which verifies the signature, pulls the fresh rows, creates a work_orders record assigned to the on-call technician, and flips the request to dispatched.

1 · The receiver (runnable as-is: python pm_webhook_target.py 9808 <secret> https://acme.sandbox.inventdb.com <token>)

import hashlib, hmac, json, sys, urllib.request
from http.server import BaseHTTPRequestHandler, HTTPServer

PORT, SECRET, INVENTDB, TOKEN = int(sys.argv[1]), sys.argv[2], sys.argv[3].rstrip("/"), sys.argv[4]
OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))  # never proxy API calls

def idb(method, path, body=None):
    req = urllib.request.Request(f"{INVENTDB}{path}", method=method,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"content-type": "application/json", "authorization": f"Bearer {TOKEN}"})
    with OPENER.open(req, timeout=15) as r:
        return json.loads(r.read() or b"null")

class Hook(BaseHTTPRequestHandler):
    def do_POST(self):
        raw = self.rfile.read(int(self.headers.get("content-length", 0)))
        ts  = self.headers.get("X-InventDB-Timestamp", "")
        # 1) VERIFY:  sha256=HMAC_SHA256(secret, "<timestamp>.<body>")
        mine = "sha256=" + hmac.new(SECRET.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(mine, self.headers.get("X-InventDB-Signature", "")):
            self.send_response(401); self.end_headers(); return
        signal = json.loads(raw)
        if signal.get("test"):                       # test pings: verify + 200, no action
            self.send_response(200); self.end_headers(); return
        # 2) PULL: the signal is data-free; fetch the actual urgent requests.
        rows = idb("POST", "/sql", {"sql":
            "SELECT _id, unit, issue FROM pms.maintenance_requests "
            "WHERE status = 'new' AND priority = 'urgent' ORDER BY _createdAt DESC LIMIT 10"})
        # 3) ACT: dispatch a technician - one work order per request, then flip status.
        for r in rows:
            wo = idb("POST", "/db/pms/work_orders", {"request_id": r["_id"], "unit": r["unit"],
                 "issue": r["issue"], "assigned_to": "tech-on-call", "sla_hours": 4, "status": "dispatched"})
            idb("PUT", "/db/pms/maintenance_requests", {"_id": r["_id"], "status": "dispatched"})
            print(f"[DISPATCH] unit {r['unit']} -> work order {wo.get('id')}")
        self.send_response(200); self.end_headers(); self.wfile.write(b'{"ok":true}')

HTTPServer(("0.0.0.0", PORT), Hook).serve_forever()

2 · Subscribe, then file a request

# subscribe (save the one-time secret into the receiver)
curl -s -X POST $BASE/api/webhooks/subscriptions -H "Authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{"target_url":"https://ops.example.com/hooks/inventdb","namespace":"pms",
       "type":"maintenance_requests","ops":["insert","update"],"debounce_ms":1000}'

# a tenant files an urgent request; this is the trigger
curl -s -X POST $BASE/db/pms/maintenance_requests -H "Authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{"unit":"9F","issue":"Main door lock jammed, tenant locked out",
       "priority":"urgent","status":"new","reported_by":"tenant-app"}'

3 · What actually happened (live transcript, v0.498)

[SIGNAL]   verified delivery 49965c89bc1543b6bc33f3ec95a37475
           scope=pms.maintenance_requests cursor=5673 hint={'changed': 1, 'ops': ['update']}
[PULL]     1 new urgent maintenance request(s)
[DISPATCH] unit 9F -> work order 43c3262f-78ef-4d60-8371-60459ba004e3
           ('Main door lock jammed, tenant locked out', SLA 4h)

request 9F   → { "status": "dispatched" }                       ← flipped by the receiver
work order   → { "unit": "9F", "assigned_to": "tech-on-call",
                 "sla_hours": 4, "status": "dispatched" }        ← created by the receiver
subscription → { "cursor": 5680, "head": 5689, "dead": false }   ← healthy, caught up

Attachments

Per-record file attachments: upload (multipart), list, fetch metadata, raw download, delete, and full-text / semantic / combined search over extracted text and embeddings. Storage is native: chunks, extracted text, and embeddings live in the _attachment* system types (_attachments, _attachment_text, _attachment_embeddings). Every attachment is owned by an existing record, addressed by that record's system _id.

POST/attach/{namespace}/{type_name}/{record_id}Bearer InventDB SOAR + InventDB Serverless

Upload a file attachment against an existing record. The request is a multipart form; the file part carries the raw bytes. On upload the engine stores chunks, extracts text (PDF / DOCX / TXT / images via OCR), and, if the embedding model is loaded, generates semantic embeddings so the file becomes content-searchable. Small text files finish inline; larger files return Pending and the background worker advances processing_state to Ready.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace, e.g. pms
type_namepathstringType name, e.g. properties
record_idpathstringOwning record's system _id (uuid), e.g. fe289741-574c-4311-9733-ad6be7e3ceb0

Request

Multipart form. Required part: file. Optional parts: description, tags (JSON array), custom_properties (JSON object), folder_path.

curl -s -X POST 'https://acme.sandbox.inventdb.com/attach/pms/properties/fe289741-574c-4311-9733-ad6be7e3ceb0' \
  -H 'Authorization: Bearer $TOKEN' \
  -F 'file=@/reports/P-5038-inspection-2026.pdf' \
  -F 'description=2026 annual inspection report' \
  -F 'tags=["inspection","2026"]' \
  -F 'folder_path=/inspections/2026'

Response 201

{
  "ok": true,
  "data": {
    "ok": true,
    "attachment_id": "att_3a422f35-4abd-41af-86e3-55b29ca8c474",
    "version": 1,
    "filename": "P-5038-inspection-2026.pdf",
    "content_type": "application/pdf",
    "size": 48213,
    "extraction_status": "Pending",
    "processing_state": "Pending",
    "file_metadata": {
      "page_count": 4,
      "author": "Blue Ridge Property Mgmt"
    }
  }
}
GET/attach/{namespace}/{type_name}/{record_id}Bearer InventDB SOAR + InventDB Serverless

List every attachment owned by a single record. Returns metadata, tags, folder_path, and per-file processing_state / extraction_status, but not the bytes. Use this to render an attachment explorer for one property, lease, or work order, then fetch bytes via the /download sub-route.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace, e.g. pms
type_namepathstringType name, e.g. properties
record_idpathstringOwning record's system _id

Request

curl -s 'https://acme.sandbox.inventdb.com/attach/pms/properties/fe289741-574c-4311-9733-ad6be7e3ceb0' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "attachments": [
    {
      "attachment_id": "att_3a422f35-4abd-41af-86e3-55b29ca8c474",
      "filename": "P-5038-inspection-2026.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "version": 1,
      "created_at": "2026-07-13T11:11:45.187886700+00:00",
      "updated_at": "2026-07-13T11:11:45.187886700+00:00",
      "description": "2026 annual inspection report",
      "folder_path": "/inspections/2026",
      "tags": ["inspection", "2026"],
      "record_id": "fe289741-574c-4311-9733-ad6be7e3ceb0",
      "processing_state": "Ready",
      "extraction_status": "Completed",
      "ai_cost_usd": 0.0
    }
  ],
  "total": 1
}
GET/attach/{namespace}/{type_name}/{record_id}/{attachment_id}Bearer InventDB SOAR + InventDB Serverless

Fetch a single attachment's metadata, tags, and extraction status. Bytes are not included; use the /download sub-route for the raw file. Handy for polling processing_state after a deferred upload until it reaches Ready.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace, e.g. pms
type_namepathstringType name, e.g. properties
record_idpathstringOwning record's system _id
attachment_idpathstringAttachment id, e.g. att_3a422f35-4abd-41af-86e3-55b29ca8c474

Request

curl -s 'https://acme.sandbox.inventdb.com/attach/pms/properties/fe289741-574c-4311-9733-ad6be7e3ceb0/att_3a422f35-4abd-41af-86e3-55b29ca8c474' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": {
    "attachment_id": "att_3a422f35-4abd-41af-86e3-55b29ca8c474",
    "filename": "P-5038-inspection-2026.pdf",
    "content_type": "application/pdf",
    "size": 48213,
    "version": 1,
    "created_at": "2026-07-13T11:11:45.187886700+00:00",
    "updated_at": "2026-07-13T11:11:45.187886700+00:00",
    "description": "2026 annual inspection report",
    "tags": ["inspection", "2026"],
    "record_id": "fe289741-574c-4311-9733-ad6be7e3ceb0",
    "processing_state": "Ready",
    "extraction_status": "Completed",
    "ai_cost_usd": 0.0
  }
}
GET/attach/{namespace}/{type_name}/{record_id}/{attachment_id}/downloadBearer InventDB SOAR + InventDB Serverless

Download the current version of an attachment as raw bytes. The response sets Content-Type to the stored MIME type and Content-Disposition: attachment so browsers trigger a save dialog. Returns 404 if the attachment does not exist.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace, e.g. pms
type_namepathstringType name, e.g. properties
record_idpathstringOwning record's system _id
attachment_idpathstringAttachment id

Request

curl -s -OJ 'https://acme.sandbox.inventdb.com/attach/pms/properties/fe289741-574c-4311-9733-ad6be7e3ceb0/att_3a422f35-4abd-41af-86e3-55b29ca8c474/download' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

HTTP/1.1 200 OK
content-type: application/pdf
content-disposition: attachment; filename="P-5038-inspection-2026.pdf"
content-length: 48213

<raw file bytes, application/octet-stream>
DELETE/attach/{namespace}/{type_name}/{record_id}/{attachment_id}Bearer InventDB SOAR + InventDB Serverless

Delete an attachment: all versions plus stored bytes, extracted text, and embeddings. This is a hard delete with no soft-delete or undo. After this call the file no longer appears in list or search results.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace, e.g. pms
type_namepathstringType name, e.g. properties
record_idpathstringOwning record's system _id
attachment_idpathstringAttachment id

Request

curl -s -X DELETE 'https://acme.sandbox.inventdb.com/attach/pms/properties/fe289741-574c-4311-9733-ad6be7e3ceb0/att_3a422f35-4abd-41af-86e3-55b29ca8c474' \
  -H 'Authorization: Bearer $TOKEN'

Response 200

{
  "ok": true,
  "data": null
}
POST/attach/{namespace}/{type_name}/searchBearer InventDB SOAR + InventDB Serverless

Search attachments across a type. search_type selects the mode: keyword (filename / description / tags via LIKE), fulltext (extracted text via LIKE), semantic (vector similarity via the MEANING() function; requires embeddings), or combined (default; weighted blend of all three). Optional filters, mime_types, tags, threshold, and per-mode weights narrow and rank results.

Path & query parameters

NameInTypeDescription
namespacepathstringNamespace, e.g. pms
type_namepathstringType name, e.g. properties

Request

curl -s -X POST 'https://acme.sandbox.inventdb.com/attach/pms/properties/search' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{
    "query": "faucet repair",
    "search_type": "combined",
    "limit": 5,
    "tags": ["inspection"],
    "mime_types": ["application/pdf", "text/plain"],
    "threshold": 0.25,
    "weights": { "keyword": 0.2, "fulltext": 0.3, "semantic": 0.5 },
    "filters": { "min_size": 100, "max_pages": 20 }
  }'
{
  "query": "faucet repair",
  "search_type": "combined",
  "limit": 5,
  "tags": ["inspection"],
  "mime_types": ["application/pdf", "text/plain"],
  "threshold": 0.25,
  "weights": { "keyword": 0.2, "fulltext": 0.3, "semantic": 0.5 },
  "filters": { "min_size": 100, "max_pages": 20 }
}

Response 200

{
  "ok": true,
  "results": [
    {
      "attachment_id": "att_3a422f35-4abd-41af-86e3-55b29ca8c474",
      "record_ns": "pms",
      "record_type": "properties",
      "record_id": "fe289741-574c-4311-9733-ad6be7e3ceb0",
      "filename": "P-5038-inspection-2026.pdf",
      "content_type": "application/pdf",
      "size": 48213,
      "version": 1,
      "created_at": "2026-07-13T11:11:45.187886700Z",
      "updated_at": "2026-07-13T11:11:45.187886700Z",
      "processing_state": "Ready",
      "extraction_status": "Ready",
      "description": "2026 annual inspection report",
      "tags": ["inspection", "2026"],
      "fulltext_score": 0.3,
      "semantic_score": 0.32360438,
      "combined_score": 0.6236044,
      "text_snippet": "Kitchen faucet leaking - work order WO-30055 opened for repair.",
      "chunk_matches": [],
      "file_metadata": { "page_count": 4 }
    }
  ],
  "total_matches": 1
}

Users (self-service)

Self-service user endpoints. Distinct from /api/users/* (admin) and /api/auth/* (auth state). The caller acts only on their own user row and only on the fields these endpoints expose: no role, activation, or password changes here.

PUT/api/me/emailBearer InventDB SOAR + InventDB Serverless

Updates the email address on the calling user's own row and nothing else. This exists separately from the admin PUT /api/users/{userId} because scheduled-report delivery fails when a regular user's _system.users.email is blank, yet a non-admin cannot use the admin endpoint. The settings page posts here from its one-click "Use my Gmail address" button (shown when Gmail OAuth is connected and the stored email is empty). Scope is strictly the caller's own email field; no role or password mutation.

Path & query parameters

None. The target user is always the authenticated caller (derived from the Bearer token); there is no namespace, id, or query parameter.

Request

curl -s -X PUT 'https://acme.sandbox.inventdb.com/api/me/email' \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'content-type: application/json' \
  -d '{ "email": "maya@pmco.example" }'
{
  "email": "maya@pmco.example"
}

Response 200

{
  "ok": true,
  "user": {
    "_id": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "username": "maya",
    "email": "maya@pmco.example",
    "role": "superadmin",
    "isActive": true,
    "createdAt": "2026-06-11T16:37:15.528274200+00:00",
    "updatedAt": "2026-07-13T11:13:56.297191200+00:00"
  }
}

Response 400: empty or malformed email

{
  "ok": false,
  "error": "email is required"
}

Response 404: user not found

{
  "ok": false,
  "error": "user not found"
}

Reports Mixed tier

Report-template CRUD, SQL-driven HTML rendering, and LLM-assisted template authoring under /api/report-templates.

POST/api/report-templatesBearer InventDB SOAR + InventDB Serverless

Persists a new SQL-driven HTML report template into _System.ReportTemplates. Body carries the complete html document (with <script type="server"> blocks), mode ("scheduled"|"interactive"), parameters, tags, and an optional per-template max_rows cap. version starts at 1 and createdBy is taken from the caller's JWT. Returns the stored document. No LLM involved.

Request

{
  "name": "Monthly Rent Roll",
  "description": "Active leases and market rent by property",
  "category": "Operational",
  "html": "<!DOCTYPE html><html><head><style>@page{size:A4;margin:14mm 12mm}</style></head><body><h1>Rent Roll</h1><table><thead><tr><th>Property</th><th>City</th><th>Market Rent</th></tr></thead><tbody><script type=\"server\">const r=query(\"SELECT property_id, city, market_rent FROM pms.properties WHERE status = :status\",{status:params.status});for(const row of r.rows){out.write('<tr><td>'+esc(row.property_id)+'</td><td>'+esc(row.city)+'</td><td>'+fmt.currency(Number(row.market_rent))+'</td></tr>');}</script></tbody></table></body></html>",
  "mode": "scheduled",
  "parameters": [
    {
      "name": "status",
      "type": "string",
      "label": "Status",
      "required": true,
      "default": "occupied"
    }
  ],
  "tags": [
    "rent-roll",
    "operational"
  ],
  "max_rows": 5000
}

Response 201

{
  "ok": true,
  "data": {
    "_id": "tpl_8f21ac",
    "name": "Monthly Rent Roll",
    "description": "Active leases and market rent by property",
    "category": "Operational",
    "html": "<!DOCTYPE html>...</html>",
    "mode": "scheduled",
    "parameters": [
      {
        "name": "status",
        "type": "string",
        "label": "Status",
        "required": true,
        "default": "occupied"
      }
    ],
    "version": 1,
    "createdBy": "user_101",
    "tags": [
      "rent-roll",
      "operational"
    ],
    "max_rows": 5000
  }
}
GET/api/report-templatesBearer InventDB SOAR + InventDB Serverless

Returns a lightweight list of saved templates (SELECT _id, name, description, category, mode, version, createdBy … LIMIT 1000) from _System.ReportTemplates. The heavy html body is NOT included; use GET /{id} to fetch a full template. No LLM involved.

Response 200

{
  "ok": true,
  "data": {
    "templates": [
      {
        "_id": "tpl_8f21ac",
        "name": "Monthly Rent Roll",
        "description": "Active leases and market rent by property",
        "category": "Operational",
        "mode": "scheduled",
        "version": 3,
        "createdBy": "user_101"
      },
      {
        "_id": "tpl_44d0e2",
        "name": "Owner Statement",
        "description": "Per-owner distribution statement",
        "category": "Finance",
        "mode": "scheduled",
        "version": 1,
        "createdBy": "user_101"
      }
    ]
  }
}
GET/api/report-templates/{id}Bearer InventDB SOAR + InventDB Serverless

Fetches the full stored template document by id, including the complete html body, parameters, tags, and version. Returns 404 when the id does not exist. No LLM involved.

Parameters

NameInTypeDescription
idpathstringTemplate id (e.g. tpl_8f21ac).

Response 200

{
  "ok": true,
  "data": {
    "_id": "tpl_8f21ac",
    "name": "Monthly Rent Roll",
    "description": "Active leases and market rent by property",
    "category": "Operational",
    "html": "<!DOCTYPE html>...</html>",
    "mode": "scheduled",
    "parameters": [
      {
        "name": "status",
        "type": "string",
        "label": "Status",
        "required": true
      }
    ],
    "version": 3,
    "createdBy": "user_101",
    "tags": [
      "rent-roll",
      "operational"
    ]
  }
}
PUT/api/report-templates/{id}Bearer InventDB SOAR + InventDB Serverless

Shallow-merges the JSON body into the stored template (any field except _id, _createdAt, createdBy which are protected) and auto-increments version. Typically used to save html/parameters/name/tags edits. Returns 404 when the id is unknown. No LLM involved.

Parameters

NameInTypeDescription
idpathstringTemplate id to update.

Request

{
  "name": "Monthly Rent Roll (v2)",
  "html": "<!DOCTYPE html>...updated...</html>",
  "tags": [
    "rent-roll",
    "finance"
  ]
}

Response 200

{
  "ok": true,
  "data": {
    "ok": true
  }
}
DELETE/api/report-templates/{id}Bearer InventDB SOAR + InventDB Serverless

Permanently removes the template from _System.ReportTemplates. Returns the deleted id on success. No LLM involved.

Parameters

NameInTypeDescription
idpathstringTemplate id to delete.

Response 200

{
  "ok": true,
  "data": {
    "deleted": "tpl_8f21ac"
  }
}
GET/api/report-templates/{id}/renderBearer InventDB SOAR + InventDB Serverless

Loads the template and renders it server-side through the boa-backed report engine under the caller's RLS principal. Parameters are supplied via query string and coerced to the declared parameter types; the template's max_rows override is honoured. Charts render as inline SVG (email_safe_charts=false) for in-browser viewing. Rendering executes the template's SQL only; it does NOT invoke the LLM, so it works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringTemplate id to render.
statusquerystringAny declared template parameter, e.g. ?status=occupied (parameter names vary per template).

Response 200

{
  "ok": true,
  "data": {
    "html": "<!DOCTYPE html>...rendered document with data...</html>",
    "meta": {
      "elapsed_ms": 42,
      "mode": "scheduled",
      "bytes": 18734
    }
  }
}
POST/api/report-templates/{id}/renderBearer InventDB SOAR + InventDB Serverless

POST variant of the render endpoint: accepts a typed/nested JSON params object instead of query-string params, avoiding URL-encoding. email_safe_charts defaults to true (email/bulk-email use); the in-app viewer sends false to get SVG charts. Executes the template SQL under the caller's RLS principal; no LLM is invoked, so it works on InventDB Serverless.

Parameters

NameInTypeDescription
idpathstringTemplate id to render.

Request

{
  "params": {
    "status": "occupied",
    "as_of_date": "2026-06-30"
  },
  "email_safe_charts": false
}

Response 200

{
  "ok": true,
  "data": {
    "html": "<!DOCTYPE html>...rendered document...</html>",
    "meta": {
      "elapsed_ms": 51,
      "mode": "scheduled",
      "bytes": 20418
    }
  }
}
POST/api/report-templates/render-ad-hocBearer InventDB SOAR + InventDB Serverless

Renders an arbitrary report-template html document supplied inline (not saved) through the same engine pipeline under the caller's RLS principal. Body is a RenderRequest: { html, params, mode?, limits?, email_safe_charts? }. Used by the AI Canvas / editor preview to render before saving. Returns 400 when html is empty, 422 when a server script crashes. Pure rendering with no LLM, so it works on InventDB Serverless.

Request

{
  "html": "<!DOCTYPE html><html><body><script type=\"server\">const r=query(\"SELECT COUNT(*) AS value FROM pms.properties\");out.write('Total properties: '+fmt.number(r.rows[0].value));</script></body></html>",
  "params": {},
  "mode": "scheduled",
  "email_safe_charts": false
}

Response 200

{
  "html": "<!DOCTYPE html><html><body>Total properties: 5,001</body></html>",
  "meta": {
    "elapsed_ms": 12,
    "mode": "scheduled",
    "bytes": 96
  }
}
POST/api/report-templates/test-queryBearer InventDB SOAR + InventDB Serverless

Editor's inline data inspector: substitutes :name params into the given SQL (string values quoted/escaped), expands CURRENT_DATE, appends a LIMIT (default 500) for non-aggregate queries, and executes it. Returns the rows, row count, and execution time. Returns 400 when sql is missing or the query errors. Straight SQL execution with no LLM, so it works on InventDB Serverless.

Request

{
  "sql": "SELECT property_id, city, state, market_rent, status FROM pms.properties WHERE status = :status",
  "params": {
    "status": "occupied"
  },
  "max_rows": 100
}

Response 200

{
  "ok": true,
  "data": {
    "rows": [
      {
        "property_id": "P-5001",
        "city": "Austin",
        "state": "TX",
        "market_rent": 2450,
        "status": "occupied"
      },
      {
        "property_id": "P-5002",
        "city": "Dallas",
        "state": "TX",
        "market_rent": 1980,
        "status": "occupied"
      }
    ],
    "count": 2,
    "executionMs": 7
  }
}
POST/api/report-templates/generateBearer InventDB SOAR only

Renders a report from either an inline html body or a saved template_id, merging params/parameters into the render. Listed among the AI report routes and gated on the AI tier: on an InventDB Serverless instance the whole report-templates nest requires AI to be enabled, so this returns 403 with the "AI features are not available on the Serverless Database tier." error. Requires html OR template_id (400 otherwise). 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "template_id": "tpl_8f21ac",
  "parameters": {
    "status": "occupied"
  }
}

Response 200

{
  "html": "<!DOCTYPE html>...rendered document...</html>",
  "meta": {
    "elapsed_ms": 45,
    "mode": "scheduled",
    "bytes": 18902
  }
}
POST/api/report-templates/from-documentBearer InventDB SOAR only

Multipart upload (file + optional name/description). The built-in LLM (vision) reads the uploaded design (PDF/DOCX/XLSX/image) and authors a complete SQL-driven HTML report-template document bound to the customer's schema, returning { html, parameters, mode }. Invokes the built-in LLM, so it returns 403 on the InventDB Serverless tier. Also returns 400 when no file is sent, 422 on text-extract failure or non-JSON model output, 502 on LLM error. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Parameters

NameInTypeDescription
filequeryfile (multipart)The design document to recreate (PDF, DOCX, XLSX, or image). Multipart form field rather than a query param.
namequerystringSuggested template name (multipart form field).
descriptionquerystringOptional description (multipart form field).

Response 200

{
  "ok": true,
  "data": {
    "html": "<!DOCTYPE html>...generated template...</html>",
    "parameters": [
      {
        "name": "as_of_date",
        "type": "date",
        "label": "As Of Date",
        "required": true
      }
    ],
    "mode": "scheduled"
  }
}
POST/api/report-templates/{id}/layout/streamBearer InventDB SOAR only

SSE endpoint. The user gives a natural-language layout instruction (optionally with pasted images/attachments); the built-in LLM returns a COMPLETE replacement html document, which is validate-rendered under RLS and, if it renders, persisted (version bumped, parameters auto-derived). Streams start/reasoning/html/saved/done (or error) events. Invokes the built-in LLM, so it returns 403 on the InventDB Serverless tier. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Parameters

NameInTypeDescription
idpathstringTemplate id being edited.

Request

{
  "instruction": "Add a bar chart of total market rent grouped by city, and use a teal accent.",
  "messages": [],
  "conversationMode": false,
  "modelFamily": "claude"
}

Response 200

event: start
data: {"template_id":"tpl_8f21ac","current_version":3,"instruction":"Add a bar chart..."}

event: reasoning
data: {"content":"Composing the report… (8s)"}

event: html
data: {"html":"<!DOCTYPE html>...</html>","bytes":21044}

event: saved
data: {"template_id":"tpl_8f21ac","version":4}

event: done
data: {}
POST/api/report-templates/promote-from-reportBearer InventDB SOAR + InventDB Serverless

Promotes a rendered ad-hoc report attachment (under _System.AIReports) into a saved template. Prefers the source-tagged sibling attachment (which keeps the live <script type="server"> bindings), auto-detects interactive vs scheduled mode from the html, auto-derives parameters from params.<name> accesses, inserts the new template, and returns { id, editorUrl }. Reads attachments and inserts a record without any LLM call, so it works on InventDB Serverless. Returns 404/400 when the source attachment is missing/empty/non-UTF-8.

Request

{
  "recordId": "rec_ai_9021",
  "attachmentId": "att_render_5567",
  "name": "Owner Statement (from canvas)"
}

Response 201

{
  "ok": true,
  "data": {
    "id": "tpl_new_77aa10",
    "editorUrl": "/report-templates?open=tpl_new_77aa10"
  }
}

AI Agent & Canvas Mixed tier

Natural-language AI agent: streaming analyst chat, NL-to-SQL, data/type/recipe generation, plus non-LLM helpers (model/config introspection, chat file staging, transactional change-set apply, and Analyze-thread persistence).

POST/ai/chat/streamBearer InventDB SOAR only

Runs the v3 tool-calling agent loop and streams progress as Server-Sent Events (each event data is a JSON AgentStep: {type, content, ...} plus tool results, SQL steps, widgets, forms, charts, and a final done). This is an alias for /ai/chat/v3/stream; the Console app calls it directly and the Tenantry proxy forwards to /ai/chat/v3/stream, so both converge on the same backend. AI config is read live from the encrypted DbSettings record; the per-message model_family (or the customer's configured default) resolves the provider/gateway path. Invokes the built-in LLM. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "messages": [
    {
      "role": "user",
      "content": "Which owners have the highest total market_value across their properties?"
    }
  ],
  "conversationMode": true,
  "modelFamily": "claude",
  "theme": "light",
  "timeZone": "America/New_York"
}

Response 200

data: {"type":"info","content":"Analyzing your question..."}

data: {"type":"sql","content":"Querying database...","sql":"SELECT o.owner_id, o.name, SUM(p.market_value) AS total_value FROM pms.properties p JOIN pms.owners o ON p.owner_id = o.owner_id GROUP BY o.owner_id, o.name ORDER BY total_value DESC LIMIT 5"}

data: {"type":"result","content":"Query returned 5 rows","rows":[{"owner_id":"O-1001","name":"Ridgeline Holdings","total_value":4820000}]}

data: {"type":"done","content":"","answer":"Ridgeline Holdings (O-1001) holds the highest total market value at $4.82M."}

POST/ai/chat/v2/streamBearer InventDB SOAR only

Legacy v2 agent loop that streams SSE AgentStep events. Uses the ToolRegistry two-tier loop with task management. AI config is read live from DbSettings; 400 if no provider/key is configured. Superseded by v3 (/ai/chat/stream) but kept for compatibility. Invokes the built-in LLM. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "messages": [
    {
      "role": "user",
      "content": "Show me all open work orders for property P-5001"
    }
  ],
  "theme": "light"
}

Response 200

data: {"type":"info","content":"Analyzing your question..."}

data: {"type":"result","content":"Query returned 3 rows","rows":[{"property_id":"P-5001","status":"open","description":"HVAC repair"}]}

data: {"type":"done","content":""}

POST/ai/chat/v3/streamBearer InventDB SOAR only

The canonical v3 tool-calling agent loop. Identical backend to /ai/chat/stream. Streams SSE AgentStep events; resolves the model via apply_model_family so each family routes to its own gateway path (Claude -> Anthropic /messages, GLM/Sparkle -> OpenAI /chat/completions). 400 if no provider is configured. Invokes the built-in LLM. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "messages": [
    {
      "role": "user",
      "content": "Vacate the tenant on lease L-7039 and start a new lease for the incoming tenant"
    }
  ],
  "modelFamily": "open"
}

Response 200

data: {"type":"info","content":"Analyzing your question..."}

data: {"type":"change_set","content":"Review the proposed changes","steps":[{"op":"update","typeName":"leases","recordId":"L-7039"}]}

data: {"type":"done","content":""}

POST/ai/chat/{tab_id}/respondBearer InventDB SOAR only

User's decision on a task that is in the awaiting_input state, keyed by the chat tab id. responseType is one of approve, deny, edit, cancel (also approve_all/answer). For edit/answer, value carries the edited SQL or answer text. Resumes (or cancels) the LLM-driven task and persists it. 403 if the caller is neither the task owner nor superadmin; 404 if no awaiting task exists for the tab. Sits behind the AI gate and resumes an LLM agent loop. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Parameters

NameInTypeDescription
tab_idpathstringChat tab id whose awaiting_input task should be resumed

Request

{
  "responseType": "edit",
  "value": "SELECT * FROM pms.leases WHERE status = 'active' LIMIT 50"
}

Response 200

{
  "status": "running",
  "taskId": "task_9f2a"
}
POST/ai/generate-sqlBearer InventDB SOAR only

Turns a natural-language question into a SQL SELECT without running it. Tries fast tiers first (exact + template cache, then the NQC neural query compiler, then the optional PSI engine) and only calls the LLM (Tier 2, tool-calling) on a miss. inferenceSource in the response reports which tier answered (Cache / InventDB NQC / InventDB Inference / External Inference). Refinements pass base_sql (bypasses all fast tiers). context narrows the schema hint; model_family overrides the model. Because the LLM is the fallback path, this route is AI-gated (403 on serverless). 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "question": "list properties in Austin, TX with market_rent over 2000, most expensive first",
  "context": {
    "namespace": "pms",
    "type": "properties"
  },
  "modelFamily": "claude"
}

Response 200

{
  "sql": "SELECT property_id, city, state, market_rent, beds, baths FROM pms.properties WHERE city = 'Austin' AND state = 'TX' AND market_rent > 2000 ORDER BY market_rent DESC",
  "inferenceSource": "External Inference"
}
POST/ai/sql-titleBearer InventDB SOAR only

Given a final SQL SELECT, returns a concise 3-7 word Title-Case label for a data-viewer grid, using the same permission-filtered schema context as the SQL generator. Results are cached per sql-hash (cached=true on a repeat hit). Best-effort: on LLM failure returns an empty title with 200 (the client falls back to its own heuristic). 400 if sql is empty. Invokes the built-in LLM on a cache miss. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "sql": "SELECT o.name, COUNT(*) AS property_count FROM pms.properties p JOIN pms.owners o ON p.owner_id = o.owner_id GROUP BY o.name ORDER BY property_count DESC"
}

Response 200

{
  "title": "Properties by Owner",
  "cached": false
}
POST/ai/generate-typesBearer InventDB SOAR only

Uses the LLM to draft a JSON array of type definitions (namespace, typeName, fields with name/type/description and optional FK references, recordCount) from a natural-language domain description. Used by the simulation/data-seeding wizard. 400 if description is empty; 500 if the model fails to return a valid JSON array (raw text echoed back). Invokes the built-in LLM. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "description": "A property management system with owners, properties, leases and tenants"
}

Response 200

{
  "types": [
    {
      "namespace": "PMS",
      "typeName": "Owner",
      "fields": [
        {
          "name": "name",
          "type": "string",
          "description": "Owner full name"
        },
        {
          "name": "email",
          "type": "string",
          "description": "Contact email"
        }
      ],
      "recordCount": 200
    },
    {
      "namespace": "PMS",
      "typeName": "Property",
      "fields": [
        {
          "name": "ownerId",
          "type": "string",
          "description": "Owning owner",
          "references": "Owner"
        },
        {
          "name": "city",
          "type": "string",
          "description": "City"
        },
        {
          "name": "marketRent",
          "type": "number",
          "description": "Monthly market rent"
        }
      ],
      "recordCount": 2500
    }
  ]
}
POST/ai/generate-dataBearer InventDB SOAR only

Streams LLM-generated realistic mock records for each supplied type definition as SSE AgentStep events. Processes parent (FK-target) types first so child records can reference real _id values; caps at 100K records/type and generates up to 50 template records per type via the model. humanReadableIds toggles CUST001-style ids vs UUIDs. Invokes the built-in LLM. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "types": [
    {
      "namespace": "pms",
      "typeName": "owners",
      "fields": [
        {
          "name": "name",
          "type": "string",
          "description": "Owner name"
        },
        {
          "name": "email",
          "type": "string",
          "description": "Email"
        }
      ],
      "recordCount": 50
    }
  ],
  "description": "property management owners",
  "humanReadableIds": true
}

Response 200

data: {"type":"info","content":"Generating simulation data for: property management owners"}

data: {"type":"info","content":"Generating 50 template records for pms.owners..."}

data: {"type":"done","content":""}

POST/ai/generate-recipeBearer InventDB SOAR only

Phase 1 of the simulation flow: derives a deterministic data-generation recipe from the supplied type definitions using Rust-side heuristics (field-name + field-type patterns and built-in word pools) and returns it plus a 10-record-per-type preview for user approval. Despite no longer calling the model, this route sits behind the AI gate and returns 403 on the InventDB Serverless tier. 400 if types is empty. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "types": [
    {
      "namespace": "pms",
      "typeName": "properties",
      "fields": [
        {
          "name": "city",
          "type": "string",
          "description": "City"
        },
        {
          "name": "marketRent",
          "type": "number",
          "description": "Rent"
        },
        {
          "name": "status",
          "type": "string",
          "description": "Status"
        }
      ],
      "recordCount": 2500
    }
  ],
  "description": "property management",
  "humanReadableIds": true
}

Response 200

{
  "recipe": {
    "types": [
      {
        "namespace": "pms",
        "typeName": "properties",
        "recordCount": 2500
      }
    ],
    "humanReadableIds": true
  },
  "preview": [
    {
      "namespace": "pms",
      "typeName": "properties",
      "records": [
        {
          "_id": "PROP001",
          "city": "Austin",
          "marketRent": 2150,
          "status": "occupied"
        }
      ]
    }
  ]
}
POST/ai/execute-simulationBearer InventDB SOAR only

Phase 2 of the simulation flow: generates records from an approved recipe and bulk-inserts them into the database, streaming SSE progress (per-type preview, then generating events at every 1% boundary, then a final done_with_answer). FK linking is preserved by processing parent types first and reusing generated _id pools. Generation itself is deterministic (recipe-driven, no model call) but the route validates AI config and sits behind the AI gate (403 on serverless). 400 if the recipe is empty. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "recipe": {
    "types": [
      {
        "namespace": "pms",
        "typeName": "properties",
        "recordCount": 2500
      }
    ],
    "humanReadableIds": true
  }
}

Response 200

data: {"type":"info","content":"Starting simulation: 1 types, 2500 total records"}

data: {"type":"generating","typeName":"properties","inserted":2500,"total":2500}

data: {"type":"done","content":"Simulation complete. Generated and inserted records across 1 types."}

GET/ai/modelsBearer InventDB SOAR + InventDB Serverless

Returns the configured model first (marked active), followed by provider-appropriate alternatives. For the InventDB gateway the alternatives are catalog-driven from the enabled model registry ('Family - LLM (Gateway)'); for direct anthropic/openai providers a small static list is returned. No LLM invocation, so this stays open on every tier.

Response 200

[
  {
    "id": "claude-opus-4-8",
    "name": "claude-opus-4-8 (active, from config.json)",
    "provider": "anthropic"
  },
  {
    "id": "glm-5.2",
    "name": "Open GLM 5.2 (Gateway)",
    "provider": "inventdb"
  }
]
GET/ai/configBearer InventDB SOAR + InventDB Serverless

Returns whether AI is configured, a masked API key, the base URL, the model, and the resolved default model family (claude / glm / sparkle / sparkle-thinking) read live from DbSettings. Never exposes the raw key. No LLM invocation; open on every tier.

Response 200

{
  "configured": true,
  "maskedKey": "sk-a...9f2c",
  "baseUrl": "https://gateway.inventdb.com/anthropic/v1",
  "model": "claude-opus-4-8",
  "modelFamily": "claude"
}
POST/ai/uploads/stageBearer InventDB SOAR + InventDB Serverless

Multipart upload of one file into the per-session, disk-backed staging store. Returns a pending_id the client embeds in '[attached: ... pending: <id>]' markers; the agent reads bytes for vision/text peeks and the canvas form re-attaches on Create. No DB writes, no OCR, no embeddings; files expire after 30 min and are capped at 25 MB (413 if larger). 400 if no file field is present. No LLM invocation; open on every tier.

Response 201

{
  "ok": true,
  "pending_id": "pf_1718900000000_3",
  "filename": "lease-L-7039.pdf",
  "content_type": "application/pdf",
  "size": 48213
}
GET/ai/uploads/{id}Bearer InventDB SOAR + InventDB Serverless

Returns the raw bytes of a staged file (cache-first, disk-fallback so it survives a restart). Used by the canvas form to re-attach the file to a newly created record. 404 if the pending file is missing or expired; 403 if the caller is not the uploader (superadmin may always fetch). No LLM invocation; open on every tier.

Parameters

NameInTypeDescription
idpathstringThe pending_id (pf_...) returned by /ai/uploads/stage

Response 200

<raw file bytes; Content-Type + Content-Disposition set from the staged file>
DELETE/ai/uploads/{id}Bearer InventDB SOAR + InventDB Serverless

Removes a staged file from the store and disk (called by the canvas form after a successful attach or when the user dismisses the form). Always returns 200 with a removed flag. No LLM invocation; open on every tier.

Parameters

NameInTypeDescription
idpathstringThe pending_id (pf_...) of the staged file to remove

Response 200

{
  "ok": true,
  "removed": true
}
POST/ai/change-set/applyBearer InventDB SOAR + InventDB Serverless

Runs an array of insert/update/delete steps inside a single InventDB transaction (all-or-nothing), then runs any attach steps post-commit (best-effort, outside the tx). This is the confirm-and-commit chokepoint for the AI canvas change-set card, so writes only land after explicit user consent. update merges partial fields over the current doc; record targets resolve by _id or by matchField business key. Returns per-step results and a transactionId. 400 if steps is empty or a data step fails (transaction rolled back); 409 on commit failure; 200 with ok:false if data committed but some attaches failed. No LLM invocation; open on every tier. An external agent authors the steps, this route just applies them.

Request

{
  "title": "Lease turnover for P-5001",
  "steps": [
    {
      "op": "update",
      "namespace": "pms",
      "typeName": "leases",
      "recordId": "L-7039",
      "matchField": "Lease_ID",
      "fields": {
        "status": "ended"
      }
    },
    {
      "op": "insert",
      "namespace": "pms",
      "typeName": "tenants",
      "fields": {
        "name": "Dana Cole",
        "email": "dana@example.com"
      }
    }
  ]
}

Response 200

{
  "ok": true,
  "results": [
    {
      "index": 0,
      "op": "update",
      "namespace": "pms",
      "typeName": "leases",
      "recordId": "L-7039",
      "ok": true
    },
    {
      "index": 1,
      "op": "insert",
      "namespace": "pms",
      "typeName": "tenants",
      "recordId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "ok": true
    }
  ],
  "transactionId": "tx_7c1e"
}
GET/ai/threadsBearer InventDB SOAR + InventDB Serverless

Returns the caller's persisted Analyze/Workbench chat threads (newest first) from _system._SoarThreads, scoped by user_id. The server reads via a system principal (bypassing RLS) but enforces per-user scoping on user_id. No LLM invocation; open on every tier.

Response 200

{
  "ok": true,
  "threads": [
    {
      "_id": "th_01",
      "user_id": "u_123",
      "label": "Owner value analysis",
      "created": "2026-06-20T14:03:00Z"
    }
  ]
}
PUT/ai/threadsBearer InventDB SOAR + InventDB Serverless

Creates or updates a single Analyze thread for the caller (the server stamps user_id from the bearer). The full thread payload (exchanges with artifacts + steps) is stored under the underscore-prefixed _exchanges blob field so a reload is a single round-trip and large payloads stay out of the index. 400 if id is missing; 500 on write failure. No LLM invocation; open on every tier.

Request

{
  "id": "th_01",
  "label": "Owner value analysis",
  "created": "2026-06-20T14:03:00Z",
  "exchanges": [
    {
      "question": "Top owners by market_value?",
      "answer": "Ridgeline Holdings leads at $4.82M."
    }
  ]
}

Response 200

{
  "ok": true,
  "id": "th_01"
}
DELETE/ai/threads/{id}Bearer InventDB SOAR + InventDB Serverless

Deletes a single Analyze thread owned by the caller (ownership checked against user_id via the system principal). Always returns 200 with ok:true; a non-owned or missing id is a silent no-op. No LLM invocation; open on every tier.

Parameters

NameInTypeDescription
idpathstringThread _id to delete (must be owned by the caller)

Response 200

{
  "ok": true
}

LLM API (OpenAI-compatible) Mixed tier

OpenAI-compatible LLM API surface (/v1): chat completions proxied to the built-in model, model listing, and local MiniLM embeddings.

POST/v1/chat/completionsBearer InventDB SOAR only

Proxies an OpenAI-style chat request through the shared LlmClient (the same client the AI agent uses), which dispatches to the configured provider (Anthropic reshapes to /v1/messages; OpenAI / InventDB Gateway / llama-server keep the /v1/chat/completions shape). Requires the messages array; temperature defaults to 0.7. Because it invokes the built-in LLM it returns HTTP 403 on the InventDB Serverless tier. Returns an OpenAI chat.completion object (usage counters are zeroed). 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "messages": [
    {
      "role": "system",
      "content": "You are a property-management assistant."
    },
    {
      "role": "user",
      "content": "Summarize the lease status for owner O-1001's properties."
    }
  ],
  "temperature": 0.7
}

Response 200

{
  "id": "chatcmpl-inventdb-3f2a9c1e-4b7d-4e21-9f0a-1c2d3e4f5a6b",
  "object": "chat.completion",
  "created": 1752403200,
  "model": "claude-opus-4-8",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Owner O-1001 holds 3 properties. P-5001 (Austin, TX) is Occupied at $2,400 market_rent; two others are Vacant."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0
  }
}
GET/v1/modelsBearer InventDB SOAR + InventDB Serverless

Returns the configured external model in OpenAI list format. Reads model metadata from LlmRouter.external_config() and does NOT invoke the LLM, so it works on all tiers including InventDB Serverless.

Response 200

{
  "object": "list",
  "data": [
    {
      "id": "claude-opus-4-8",
      "object": "model",
      "created": 1752403200,
      "owned_by": "external"
    }
  ]
}
POST/v1/embeddingsBearer InventDB SOAR + InventDB Serverless

Generates embeddings using the local all-MiniLM-L6-v2 runtime via LazyEmbeddingGenerator. Accepts a single string or an array of strings in the input field. This is the local embedding model (part of the serverless OCR+MiniLM carve-out), NOT the built-in chat/completion LLM, so it works on the InventDB Serverless tier. Returns 503 if the MiniLM model is not installed. Usage token counts are approximated by whitespace-split word count.

Request

{
  "input": [
    "Property P-5001 in Austin, TX with market_rent 2400",
    "Owner O-1001 contact info"
  ]
}

Response 200

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [
        0.0123,
        -0.0456,
        0.0789
      ]
    },
    {
      "object": "embedding",
      "index": 1,
      "embedding": [
        -0.0011,
        0.0342,
        -0.0567
      ]
    }
  ],
  "model": "text-embedding-minilm",
  "usage": {
    "prompt_tokens": 12,
    "total_tokens": 12
  }
}

Agent Tasks Any tier

Autonomous agent task management: list, inspect, cancel, and delete persisted long-running agent tasks (no LLM invoked; works on every tier).

GET/agent/tasksBearer InventDB SOAR + InventDB Serverless

Returns all persisted agent tasks owned by the authenticated user (scoped by claims.sub). Each task is a resumable, auditable record of a promoted 'full task mode' agent run: its state, steps, files created, iteration count, and token/time totals. Reads persisted records only; invokes no LLM, so it works on the InventDB Serverless tier.

Response 200

{
  "tasks": [
    {
      "id": "3f2a9c14-7b0e-4d2a-9c1f-8e6b5a1d0042",
      "chatTabId": "direct",
      "chatMessageSeq": 0,
      "owner": "maya@pmco.example",
      "ownerRole": "admin",
      "title": "Analyze vacant units and market rent by city",
      "goal": "Give me a full analysis of vacant properties and their market rent across cities",
      "state": "Done",
      "steps": [],
      "filesCreated": [],
      "variables": {},
      "maxIterations": 50,
      "iterationCount": 8,
      "timeoutSeconds": 300,
      "confirmationPolicy": "Always",
      "createdAt": "2026-07-13T09:12:04Z",
      "updatedAt": "2026-07-13T09:13:41Z",
      "completedAt": "2026-07-13T09:13:41Z",
      "totalLlmTokens": 18422,
      "totalExecutionTimeMs": 96540
    }
  ]
}
GET/agent/tasks/{id}Bearer InventDB SOAR + InventDB Serverless

Returns the full AgentTask record for the given task id, including its complete step audit trail, task-scoped variables, and any files created. Access is restricted to the task owner or an admin/superadmin (403 otherwise); an unknown id returns 404. Reads a persisted record only; no LLM is invoked, so it works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringThe task UUID (from the list endpoint's `id` field).

Response 200

{
  "id": "3f2a9c14-7b0e-4d2a-9c1f-8e6b5a1d0042",
  "chatTabId": "direct",
  "chatMessageSeq": 0,
  "owner": "maya@pmco.example",
  "ownerRole": "admin",
  "title": "Analyze vacant units and market rent by city",
  "goal": "Give me a full analysis of vacant properties and their market rent across cities",
  "state": "Done",
  "steps": [
    {
      "stepNumber": 1,
      "toolName": "sql_query",
      "input": {
        "sql": "SELECT city, AVG(market_rent) FROM pms.properties WHERE status = 'vacant' GROUP BY city"
      }
    }
  ],
  "filesCreated": [],
  "variables": {},
  "maxIterations": 50,
  "iterationCount": 8,
  "timeoutSeconds": 300,
  "confirmationPolicy": "Always",
  "createdAt": "2026-07-13T09:12:04Z",
  "updatedAt": "2026-07-13T09:13:41Z",
  "completedAt": "2026-07-13T09:13:41Z",
  "totalLlmTokens": 18422,
  "totalExecutionTimeMs": 96540
}
POST/agent/tasks/{id}/cancelBearer InventDB SOAR + InventDB Serverless

Marks the task as Cancelled and persists the change, stopping any further agent iterations for it. Restricted to the task owner or a superadmin (403 otherwise); an unknown id returns 404. This only flips persisted task state; it invokes no LLM and works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringThe task UUID to cancel.

Response 200

{
  "status": "cancelled",
  "taskId": "3f2a9c14-7b0e-4d2a-9c1f-8e6b5a1d0042"
}
DELETE/agent/tasks/{id}Bearer InventDB SOAR + InventDB Serverless

Permanently deletes the persisted agent task record. Restricted to the task owner or a superadmin (403 otherwise); an unknown id returns 404. Deletes a stored record only; no LLM is invoked, so it works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringThe task UUID to delete.

Response 200

{
  "status": "deleted",
  "taskId": "3f2a9c14-7b0e-4d2a-9c1f-8e6b5a1d0042"
}

Gmail Integration Any tier

Per-user Gmail OAuth connect plus send, bulk-send, list, and read endpoints backed by stored refresh tokens.

GET/api/gmail/statusBearer InventDB SOAR + InventDB Serverless

Returns whether the authenticated user has connected Gmail, and if so the connected email address and the Unix-seconds timestamp of when the connection was made. Reads the stored token record from _system.user_integrations keyed by gmail:{username}; never touches the built-in LLM.

Response 200

{
  "connected": true,
  "email": "manager@acme-pm.com",
  "connected_at": 1749600000
}
GET/api/gmail/oauth-configBearer InventDB SOAR + InventDB Serverless

Returns the Google OAuth client_id and the requested Gmail scopes (gmail.send + gmail.readonly) plus a configured flag indicating whether the server has both client_id and client_secret resolved (from DbSettings google_oauth, env fallback). The browser uses this to initialize Google Identity Services initCodeClient. No LLM involved.

Response 200

{
  "client_id": "1234567890-abc.apps.googleusercontent.com",
  "scopes": "https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/gmail.readonly",
  "configured": true
}
POST/api/gmail/exchange-codeBearer InventDB SOAR + InventDB Serverless

Accepts the one-shot authorization code obtained by the browser via Google Identity Services (ux_mode popup, redirect_uri=postmessage), exchanges it at Google's token endpoint for an access + refresh token pair, fetches the connected Gmail address, and persists the tokens (encrypted at rest) in _system.user_integrations. Returns 503 if OAuth is not configured, 400 if the exchange fails or Google returns no refresh token. Pure OAuth connect flow; no built-in LLM call.

Request

{
  "code": "4/0AeanS0b-oneShotAuthCodeFromGoogle"
}

Response 200

{
  "connected": true,
  "email": "manager@acme-pm.com",
  "connected_at": 1749600000
}
POST/api/gmail/revokeBearer InventDB SOAR + InventDB Serverless

Best-effort revokes the stored refresh token at Google's revoke endpoint, then always deletes the local token record from _system.user_integrations for the current user. Idempotent: returns ok:true even if no tokens were stored. No LLM involved.

Response 200

{
  "ok": true
}
POST/api/gmail/sendBearer InventDB SOAR + InventDB Serverless

Sends one email through the user's connected Gmail. The RFC-2822 message is built server-side with an RFC 2047 encoded-word Subject and a deterministic Message-ID (<wf-<nonce>@inventdb.local>) for reply-thread correlation. Optional attachments reference existing InventDB attachments by namespace + attachment_id and are fetched from NativeAttachmentStore and MIME-embedded. When INVENTDB_DEMO_SAFE_EMAIL is set, recipients are overridden. Auto-refreshes the access token first (401 on failure), 503 if OAuth unconfigured, 502 on Gmail send failure. This is a direct Gmail API send rather than an LLM call.

Request

{
  "to": "owner@acme-pm.com",
  "cc": [],
  "bcc": [],
  "subject": "Past Due on Lease L-7011 at 42 Elm St",
  "body_html": "<p>Hi Jane, rent for P-5001 (market_rent $2,150) is past due.</p>",
  "attachments": [
    {
      "namespace": "pms",
      "attachment_id": "att-9f2c",
      "filename": "statement-P-5001.pdf",
      "content_type": "application/pdf"
    }
  ]
}

Response 200

{
  "message_id": "18f3a2b9c1d4e5f6",
  "thread_id": "18f3a2b9c1d4e5f6",
  "rfc_message_id": "<wf-1749600000123456789@inventdb.local>"
}
POST/api/gmail/bulk-sendBearer InventDB SOAR + InventDB Serverless

Accepts an emails array (each a fully substituted SendEmailInput) and dispatches them serially through the connected Gmail account to stay under Gmail's per-user send quota. Refreshes the access token once for the whole batch and returns a per-row success/failure result so the UI can surface cell-by-cell status. 400 if the array is empty, 503 if OAuth unconfigured, 401 on token refresh failure. Each send is a direct Gmail API call; no LLM involved (the frontend already rendered the bodies).

Request

{
  "emails": [
    {
      "to": "owner1@acme-pm.com",
      "subject": "Rent reminder P-5001",
      "body_html": "<p>P-5001 market_rent $2,150 due for owner O-1001.</p>"
    },
    {
      "to": "owner2@acme-pm.com",
      "subject": "Rent reminder P-5002",
      "body_html": "<p>P-5002 market_rent $1,875 due for owner O-1002.</p>"
    }
  ]
}

Response 200

{
  "sent": 2,
  "failed": 0,
  "results": [
    {
      "to": "owner1@acme-pm.com",
      "ok": true,
      "message_id": "18f3a2b9c1d4e5f6"
    },
    {
      "to": "owner2@acme-pm.com",
      "ok": true,
      "message_id": "18f3a2b9c1d4e5aa"
    }
  ]
}
GET/api/gmail/messagesBearer InventDB SOAR + InventDB Serverless

Lists recent messages from the connected Gmail account within the last `days` window, optionally filtered by a Gmail search query (from:, to:, subject:, etc.). Each result carries decoded headers (RFC 2047 + mojibake-repaired), snippet, full decoded body (text/plain preferred, HTML stripped to plain), and correlation headers (rfc_message_id, in_reply_to, references). days is clamped 1-365, max clamped 1-50. Auto-refreshes token; 503 if OAuth unconfigured, 401 on refresh failure, 502 on Gmail list failure. Read-only Gmail API access; no LLM.

Parameters

NameInTypeDescription
daysqueryintegerLook-back window in days (default 7, clamped 1-365)
qquerystringOptional Gmail search query appended to newer_than filter (e.g. from:owner@acme-pm.com)
maxqueryintegerMax messages to fetch (default 20, clamped 1-50)

Response 200

[
  {
    "id": "18f3a2b9c1d4e5f6",
    "thread_id": "18f3a2b9c1d4e5f6",
    "from": "owner@acme-pm.com",
    "to": "manager@acme-pm.com",
    "subject": "Re: Past Due on Lease L-7011",
    "date": "2026-06-20T14:03:00-07:00",
    "snippet": "I can pay by Friday for P-5001...",
    "body": "I can pay by Friday for P-5001. Please confirm the amount.",
    "rfc_message_id": "<CAF123@mail.gmail.com>",
    "in_reply_to": "<wf-1749600000123456789@inventdb.local>"
  }
]
GET/api/gmail/messages/{id}Bearer InventDB SOAR + InventDB Serverless

Fetches a single message by Gmail id (format=full), returning meta (headers, snippet, decoded body) plus separate body_text and body_html fields. Used by the expand action on the EmailSummaryCard. Auto-refreshes token; 503 if OAuth unconfigured, 401 on refresh failure, 502 on Gmail get failure. Read-only Gmail API access; no LLM.

Parameters

NameInTypeDescription
idpathstringGmail internal message id

Response 200

{
  "meta": {
    "id": "18f3a2b9c1d4e5f6",
    "thread_id": "18f3a2b9c1d4e5f6",
    "from": "owner@acme-pm.com",
    "to": "manager@acme-pm.com",
    "subject": "Re: Past Due on Lease L-7011",
    "date": "2026-06-20T14:03:00-07:00",
    "snippet": "I can pay by Friday for P-5001...",
    "body": "I can pay by Friday for P-5001. Please confirm the amount.",
    "in_reply_to": "<wf-1749600000123456789@inventdb.local>"
  },
  "body_text": "I can pay by Friday for P-5001. Please confirm the amount.",
  "body_html": "<div>I can pay by Friday for P-5001. Please confirm the amount.</div>"
}

Calendar Integration Any tier

Google Calendar integration: per-user OAuth connect plus list/create/update/delete events and free/busy; no built-in LLM involved.

GET/api/calendar/statusBearer InventDB SOAR + InventDB Serverless

Returns whether the authenticated user has a stored Google Calendar OAuth grant. When connected, includes the user's primary calendar email and the connect timestamp (unix seconds). Reads token record `_system.user_integrations` keyed by `gcal:{username}`. No LLM.

Response 200

{
  "connected": true,
  "email": "maya@pmco.example",
  "connected_at": 1750636800
}
GET/api/calendar/oauth-configBearer InventDB SOAR + InventDB Serverless

Returns the server-side Google OAuth client_id and requested Calendar scopes so the frontend can launch the consent popup. `configured` is false when no Google OAuth client is set (via DbSettings google_oauth or GOOGLE_OAUTH_*/GMAIL_OAUTH_* env). The OAuth client is shared with Gmail. No LLM.

Response 200

{
  "client_id": "1234567890-abcdef.apps.googleusercontent.com",
  "scopes": "https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly",
  "configured": true
}
POST/api/calendar/exchange-codeBearer InventDB SOAR + InventDB Serverless

Exchanges the OAuth `code` (from the postmessage popup flow) for tokens at Google, captures the primary calendar email, and persists a per-user CalendarTokens record keyed by `gcal:{username}`. Returns 503 when Google OAuth is not configured, and 400 when the exchange fails or Google returns no refresh_token (needs access_type=offline + prompt=consent). No LLM.

Request

{
  "code": "4/0Aea...authorization_code"
}

Response 200

{
  "connected": true,
  "email": "maya@pmco.example",
  "connected_at": 1750636800
}
POST/api/calendar/revokeBearer InventDB SOAR + InventDB Serverless

Best-effort revokes the stored refresh token at Google's revoke endpoint and deletes the local CalendarTokens record for the user. Always returns ok:true. No LLM.

Response 200

{
  "ok": true
}
GET/api/calendar/calendarsBearer InventDB SOAR + InventDB Serverless

Refreshes the access token if needed, then lists the user's calendars (minAccessRole=writer, up to 50) via Google's calendarList API. Returns 503 if OAuth not configured, 401 if Calendar not connected / token refresh fails, 502 on upstream Google errors. No LLM.

Response 200

[
  {
    "id": "maya@pmco.example",
    "summary": "June (Property Manager)",
    "primary": true,
    "access_role": "owner",
    "time_zone": "America/New_York",
    "background_color": "#9fe1e7"
  }
]
GET/api/calendar/eventsBearer InventDB SOAR + InventDB Serverless

Lists events for a calendar (defaults to `primary`) between timeMin and timeMax (RFC 3339), expanding recurrences (singleEvents=true) and ordering by start time. Optional free-text `q` filter and `max` cap (1..250, default 50). Returns 503 if OAuth not configured, 401 if not connected, 502 on Google errors. No LLM.

Parameters

NameInTypeDescription
calendarIdquerystringTarget calendar id; defaults to 'primary'
timeMinquerystringWindow start, RFC 3339 (required)
timeMaxquerystringWindow end, RFC 3339 (required)
qquerystringOptional free-text search filter
maxqueryintegerMax events to return, 1..250 (default 50)

Response 200

[
  {
    "id": "abc123evt",
    "status": "confirmed",
    "html_link": "https://www.google.com/calendar/event?eid=abc123evt",
    "summary": "Unit inspection P-5001",
    "location": "P-5001, Austin, TX",
    "start": "2026-07-15T10:00:00-05:00",
    "end": "2026-07-15T10:30:00-05:00",
    "time_zone": "America/Chicago",
    "attendees": [
      {
        "email": "owner.o-1001@pmco.example",
        "display_name": "O-1001",
        "response_status": "needsAction"
      }
    ],
    "creator_email": "maya@pmco.example",
    "organizer_email": "maya@pmco.example",
    "all_day": false
  }
]
POST/api/calendar/eventsBearer InventDB SOAR + InventDB Serverless

Creates an event on the target calendar (defaults to `primary`), supporting timed or all-day events, IANA time_zone, attendees (sendUpdates=all so Google emails invites), and optional Google Meet link (add_meet). Under demo-safe mode all attendee addresses are rewritten to the configured safe address. Returns 503 if OAuth not configured, 401 if not connected, 502 on Google errors. No LLM.

Request

{
  "calendar_id": "primary",
  "summary": "Rent-follow-up call O-1001",
  "description": "Discuss market_rent adjustment for P-5001",
  "location": "P-5001, Austin, TX",
  "start": "2026-07-16T14:00:00",
  "end": "2026-07-16T14:30:00",
  "time_zone": "America/Chicago",
  "all_day": false,
  "attendees": [
    "owner.o-1001@pmco.example"
  ],
  "add_meet": true
}

Response 200

{
  "id": "def456evt",
  "status": "confirmed",
  "html_link": "https://www.google.com/calendar/event?eid=def456evt",
  "summary": "Rent-follow-up call O-1001",
  "start": "2026-07-16T14:00:00-05:00",
  "end": "2026-07-16T14:30:00-05:00",
  "time_zone": "America/Chicago",
  "attendees": [
    {
      "email": "owner.o-1001@pmco.example",
      "response_status": "needsAction"
    }
  ],
  "conference_link": "https://meet.google.com/abc-defg-hij",
  "creator_email": "maya@pmco.example",
  "organizer_email": "maya@pmco.example",
  "all_day": false
}
PATCH/api/calendar/events/{id}Bearer InventDB SOAR + InventDB Serverless

Partially updates an event by id on the target calendar (defaults to `primary`). Any of summary/description/location/attendees may be set; start and end must be supplied together (Google rejects only-one) and all_day toggles date vs dateTime shapes. sendUpdates=all notifies attendees. Returns 503 if OAuth not configured, 401 if not connected, 502 on Google errors. No LLM.

Parameters

NameInTypeDescription
idpathstringGoogle Calendar event id

Request

{
  "calendar_id": "primary",
  "summary": "Rent-follow-up call O-1001 (rescheduled)",
  "start": "2026-07-17T15:00:00",
  "end": "2026-07-17T15:30:00",
  "time_zone": "America/Chicago",
  "all_day": false
}

Response 200

{
  "id": "def456evt",
  "status": "confirmed",
  "html_link": "https://www.google.com/calendar/event?eid=def456evt",
  "summary": "Rent-follow-up call O-1001 (rescheduled)",
  "start": "2026-07-17T15:00:00-05:00",
  "end": "2026-07-17T15:30:00-05:00",
  "time_zone": "America/Chicago",
  "attendees": [
    {
      "email": "owner.o-1001@pmco.example",
      "response_status": "needsAction"
    }
  ],
  "creator_email": "maya@pmco.example",
  "organizer_email": "maya@pmco.example",
  "all_day": false
}
DELETE/api/calendar/events/{id}Bearer InventDB SOAR + InventDB Serverless

Deletes an event by id from the target calendar (defaults to `primary`); sendUpdates=all cancels the invite for attendees. A Google 410 Gone (already deleted) is treated as success. Returns 503 if OAuth not configured, 401 if not connected, 502 on other Google errors. No LLM.

Parameters

NameInTypeDescription
idpathstringGoogle Calendar event id
calendarIdquerystringTarget calendar id; defaults to 'primary'

Response 200

{
  "ok": true
}
POST/api/calendar/bulk-eventsBearer InventDB SOAR + InventDB Serverless

Creates a list of pre-rendered events sequentially against the user's calendar (sequential to respect Google's per-user write throttling). Returns per-item results with saved/failed counts. Rejects an empty events array with 400. Returns 503 if OAuth not configured, 401 if not connected. Each event uses the same CreateEventInput shape as POST /events. No LLM.

Request

{
  "events": [
    {
      "summary": "Inspection P-5001",
      "start": "2026-07-20T09:00:00",
      "end": "2026-07-20T09:30:00",
      "time_zone": "America/Chicago",
      "attendees": [
        "owner.o-1001@pmco.example"
      ]
    },
    {
      "summary": "Inspection P-5002",
      "start": "2026-07-20T11:00:00",
      "end": "2026-07-20T11:30:00",
      "time_zone": "America/Chicago"
    }
  ]
}

Response 200

{
  "saved": 2,
  "failed": 0,
  "results": [
    {
      "summary": "Inspection P-5001",
      "ok": true,
      "id": "evt-p5001",
      "html_link": "https://www.google.com/calendar/event?eid=evt-p5001"
    },
    {
      "summary": "Inspection P-5002",
      "ok": true,
      "id": "evt-p5002",
      "html_link": "https://www.google.com/calendar/event?eid=evt-p5002"
    }
  ]
}

Twilio / SMS Any tier

Bring-your-own-Twilio SMS integration: per-user credential storage, number listing, single/bulk SMS send, message history, and a public signature-verified inbound webhook.

GET/api/twilio/statusBearer InventDB SOAR + InventDB Serverless

Returns whether the authenticated user has stored Twilio credentials, along with the connected Account SID, Twilio friendly name, default 'From' number, whether an auth token is present (needed for webhook signature verification), and the connection timestamp. When not connected, all fields are null/false. No LLM involved.

Response 200

{
  "connected": true,
  "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "friendly_name": "InventDB PMS Outbound",
  "default_from": "+18885550100",
  "has_auth_token": true,
  "connected_at": 1751328000
}
POST/api/twilio/store-credentialsBearer InventDB SOAR + InventDB Serverless

Persists the user's Twilio credentials (encrypt-stored in _system.user_integrations keyed by twilio:{username}). Requires account_sid (must start with 'AC' and be 34 chars) plus EITHER an auth_token OR an api_key_sid+api_key_secret pair. Credentials are validated live against GET /Accounts/{SID}.json before persisting; a 401 is returned if Twilio rejects them. A previously-configured default_from is preserved across re-saves. Returns the same shape as /status. Configures an external provider (Twilio) rather than the built-in LLM.

Request

{
  "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "api_key_sid": "SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "api_key_secret": "your-api-key-secret",
  "auth_token": "your-account-auth-token"
}

Response 200

{
  "connected": true,
  "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "friendly_name": "InventDB PMS Outbound",
  "default_from": null,
  "has_auth_token": true,
  "connected_at": 1751328000
}
POST/api/twilio/revokeBearer InventDB SOAR + InventDB Serverless

Deletes the current user's stored Twilio credentials from _system.user_integrations. Idempotent; always returns ok:true. No LLM involved.

Response 200

{
  "ok": true
}
GET/api/twilio/numbersBearer InventDB SOAR + InventDB Serverless

Fetches up to 50 IncomingPhoneNumbers from the user's Twilio account (GET /Accounts/{SID}/IncomingPhoneNumbers.json), returning each number's SID, E.164 phone number, friendly name, and whether it is SMS-capable. Returns 401 if Twilio is not connected, 502 if the Twilio API call fails. No LLM involved.

Response 200

[
  {
    "sid": "PNxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "phone_number": "+18885550100",
    "friendly_name": "PMS Notifications",
    "capabilities_sms": true
  }
]
POST/api/twilio/default-fromBearer InventDB SOAR + InventDB Serverless

Sets the default outbound 'From' number used by /send and /bulk-send when a message omits an explicit 'from'. Persists to the user's stored credentials. Returns 401 if Twilio is not connected. No LLM involved.

Request

{
  "phone_number": "+18885550100"
}

Response 200

{
  "ok": true,
  "default_from": "+18885550100"
}
POST/api/twilio/sendBearer InventDB SOAR + InventDB Serverless

Sends one SMS via the user's Twilio account (POST /Accounts/{SID}/Messages.json). Body requires 'to' and 'body'; 'from' is optional and falls back to the stored default_from (error if neither is set). Returns the Twilio message SID, delivery status, resolved to/from numbers, and segment count. Returns 401 if not connected, 502 on Twilio API failure. Uses the external Twilio API rather than the built-in LLM, so it works on InventDB Serverless.

Request

{
  "to": "+14155550188",
  "from": "+18885550100",
  "body": "Reminder: rent for P-5001 (Austin, TX) is due. Balance $2450."
}

Response 200

{
  "sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "queued",
  "to": "+14155550188",
  "from": "+18885550100",
  "num_segments": 1
}
POST/api/twilio/bulk-sendBearer InventDB SOAR + InventDB Serverless

Sends an array of SMS messages sequentially, each with its own to/body/optional from. Returns per-message results (ok flag, SID or error, segment count) plus aggregate sent/failed counts. Returns 400 if the messages array is empty, 401 if not connected. Individual message failures are captured in the results array rather than failing the whole request. External Twilio API, no built-in LLM, so it works on InventDB Serverless.

Request

{
  "messages": [
    {
      "to": "+14155550188",
      "body": "Rent reminder for owner O-1001."
    },
    {
      "to": "+14155550190",
      "from": "+18885550100",
      "body": "Work order update for P-5001."
    }
  ]
}

Response 200

{
  "sent": 2,
  "failed": 0,
  "results": [
    {
      "to": "+14155550188",
      "ok": true,
      "sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "num_segments": 1
    },
    {
      "to": "+14155550190",
      "ok": true,
      "sid": "SMyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
      "num_segments": 1
    }
  ]
}
GET/api/twilio/messagesBearer InventDB SOAR + InventDB Serverless

Lists recent messages from the user's Twilio account (GET /Accounts/{SID}/Messages.json), filtered by a rolling day window and optional from/to numbers. Returns SID, direction, from/to, body, status, date_sent, segment count, and price/price_unit per message. Returns 401 if not connected, 502 on Twilio failure. No built-in LLM, so it works on InventDB Serverless.

Parameters

NameInTypeDescription
daysqueryintegerRolling window of days to look back (DateSent>=). Defaults to 7; minimum enforced as 1.
fromquerystringOptional filter; only messages sent from this E.164 number.
toquerystringOptional filter; only messages sent to this E.164 number.
maxqueryintegerMax rows to fetch (PageSize). Defaults to 50; clamped to 1..=100.

Response 200

[
  {
    "sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "direction": "outbound-api",
    "from": "+18885550100",
    "to": "+14155550188",
    "body": "Rent reminder for P-5001.",
    "status": "delivered",
    "date_sent": "Mon, 13 Jul 2026 14:02:11 +0000",
    "num_segments": 1,
    "price": "-0.00790",
    "price_unit": "USD"
  }
]
POST/twilio-webhook/incomingpublic InventDB SOAR + InventDB Serverless

Public route (no JWT) that Twilio POSTs as application/x-www-form-urlencoded when one of the user's numbers receives an SMS. The user is identified via the AccountSid form field; the X-Twilio-Signature header is verified with HMAC-SHA1 over the full request URL plus sorted params keyed by the user's auth_token (honouring X-Forwarded-Proto/Host for reverse-proxy setups). On success a stripped record (account_sid, message_sid, from, to, body, num_segments, num_media, received_at, username) is persisted to _system.twilio_incoming as an audit/compliance log and an empty TwiML-equivalent 200 is returned. Missing AccountSid => 400; unknown account => 404; signature mismatch => 401. No built-in LLM involved.

Request

{
  "AccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "MessageSid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "From": "+14155550188",
  "To": "+18885550100",
  "Body": "Tenant reply: I'll pay by Friday.",
  "NumSegments": "1",
  "NumMedia": "0"
}

Response 200

{
  "ok": true
}

Web Search Any tier

Per-user opt-in toggle for the AI agent's web-search tool. Searches execute server-side via Amazon Bedrock AgentCore (no browser-side key, no client egress); the agent loop that consumes the results is the LLM-gated part.

GET/api/websearch/statusBearer InventDB SOAR + InventDB Serverless

Returns whether the AI agent's hosted web-search tool is enabled for the authenticated user, plus whether they have acknowledged the data-leaves-tenant consent dialog and when. Reads a single per-user document from _system.user_integrations keyed by websearch:{username}; if none exists, returns enabled=false/consented=false. Pure settings read; does not invoke the built-in LLM, so it works on the InventDB Serverless tier.

Response 200

{
  "enabled": true,
  "consented": true,
  "consented_at": 1718000000
}
POST/api/websearch/enableBearer InventDB SOAR + InventDB Serverless

Turns on the opt-in web-search integration for the authenticated user. Sets enabled=true on the per-user _system.user_integrations document (creating it if absent). Records the consent timestamp the first time it is enabled and preserves that timestamp across later disable/re-enable cycles so the consent dialog is not re-shown. This only flips a settings flag that the agent loop later reads to decide whether to run server-side web search via Amazon Bedrock AgentCore; the route itself does not call the built-in LLM, so it works on the InventDB Serverless tier.

Request

{}

Response 200

{
  "ok": true,
  "enabled": true,
  "consented_at": 1718000000
}
POST/api/websearch/disableBearer InventDB SOAR + InventDB Serverless

Turns off the web-search integration for the authenticated user by setting enabled=false on the per-user _system.user_integrations document. The original consent timestamp is preserved so the user is not re-prompted if they re-enable later. Settings-write only; does not invoke the built-in LLM, so it works on the InventDB Serverless tier.

Request

{}

Response 200

{
  "ok": true,
  "enabled": false
}

AI Usage Metrics Any tier

Read-only AI usage/cost analytics, per-user/type/model breakdowns, quota management, and Chorus plan-cache savings; no LLM invoked, so it works on the InventDB Serverless tier (reports zero AI usage).

GET/api/ai-metrics/usageBearer InventDB SOAR + InventDB Serverless

Rolls up _system.AIUsage into a single period summary: request count, input/output/cached token totals, cache hit rate, average latency, and USD cost rollups (total/input/output). Reports mode 'local' or 'external' depending on whether a local LLM router is registered. Aggregation is done in Rust (GROUP BY model, no WHERE) to sidestep a known WHERE+SUM engine bug, so the period param is accepted but not yet filtered. Read-only reporting; invokes no LLM, works on serverless (reports zeros).

Parameters

NameInTypeDescription
periodquerystringLookback window token, e.g. 30d, 7d, 24h. Defaults to 30d. Currently echoed in the response but not applied to filtering.

Response 200

{
  "mode": "external",
  "period": "30d",
  "totalRequests": 412,
  "totalInputTokens": 1840293,
  "totalOutputTokens": 96114,
  "totalCachedTokens": 1620004,
  "cacheHitRate": 46.81,
  "avgLatencyMs": 2143,
  "totalCostUsd": 12.45,
  "totalInputCostUsd": 9.124,
  "totalOutputCostUsd": 3.326
}
GET/api/ai-metrics/dailyBearer InventDB SOAR + InventDB Serverless

Returns a per-day series (bucketed by the YYYY-MM-DD prefix of _createdAt) of request count, input/output/cached tokens, average latency, and total USD cost. Raw rows are fetched and aggregated in Rust because GROUP BY with scalar functions plus SUM hits a known engine bug. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringLookback window token (30d/7d/24h). Defaults to 30d; echoed back but not applied to filtering.

Response 200

{
  "period": "30d",
  "days": [
    {
      "date": "2026-07-11",
      "requests": 22,
      "inputTokens": 84021,
      "outputTokens": 5140,
      "cachedTokens": 71002,
      "avgLatencyMs": 1980,
      "totalCostUsd": 0.6412
    },
    {
      "date": "2026-07-12",
      "requests": 31,
      "inputTokens": 120440,
      "outputTokens": 7210,
      "cachedTokens": 99880,
      "avgLatencyMs": 2210,
      "totalCostUsd": 0.9033
    }
  ]
}
GET/api/ai-metrics/modelsBearer InventDB SOAR + InventDB Serverless

Returns one row per model from _system.AIUsage (GROUP BY model): request count, input/output/cached token sums, total USD cost (rounded to 4 decimals) and average latency. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringLookback window token; defaults to 30d, echoed but not applied to filtering.

Response 200

{
  "period": "30d",
  "models": [
    {
      "model": "claude-opus-4-8",
      "requests": 210,
      "inputTokens": 980221,
      "outputTokens": 52110,
      "cachedTokens": 860003,
      "totalCostUsd": 8.421,
      "avgLatencyMs": 2560
    }
  ]
}
GET/api/ai-metrics/healthBearer InventDB SOAR + InventDB Serverless

Lightweight health probe reporting whether a local LLM is available and the resulting mode ('local' vs 'external'). Does not query any table and does not invoke the LLM; works on serverless.

Response 200

{
  "status": "ok",
  "mode": "external",
  "localLlmAvailable": false
}
GET/api/ai-metrics/usage-consolidatedBearer InventDB SOAR + InventDB Serverless

Same summary shape as /usage but sourced from the gateway's central AIGateway.Usage ledger, scoped to the calling customer via WHERE clientId = <jwt.sub> (RLS-enforced). Rows are post-filtered by the period threshold in memory and cost is computed on-read from the pricing rate cache. Missing table / RLS-deny degrades to zeroed metrics rather than a 500. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringWindow token: 24h/1d, 7d, 30d, 90d. Unrecognized tokens fall back to all-time. Defaults to 30d.

Response 200

{
  "mode": "gateway",
  "period": "30d",
  "totalRequests": 88,
  "totalInputTokens": 420110,
  "totalOutputTokens": 21044,
  "totalCachedTokens": 360020,
  "cacheHitRate": 46.15,
  "avgLatencyMs": 2050,
  "totalCostUsd": 3.21,
  "totalInputCostUsd": 2.401,
  "totalOutputCostUsd": 0.809
}
GET/api/ai-metrics/daily-consolidatedBearer InventDB SOAR + InventDB Serverless

Daily series (bucketed by the YYYY-MM-DD prefix of the gateway's rfc3339 timestamp) from AIGateway.Usage, scoped to the calling customer via clientId and post-filtered by the period threshold. Cost computed on-read from the pricing cache. Missing table degrades to an empty series. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringWindow token: 24h/1d, 7d, 30d, 90d. Defaults to 30d; unrecognized falls back to all-time.

Response 200

{
  "period": "30d",
  "days": [
    {
      "date": "2026-07-12",
      "requests": 12,
      "inputTokens": 60110,
      "outputTokens": 3021,
      "cachedTokens": 50004,
      "avgLatencyMs": 1990,
      "totalCostUsd": 0.4412
    }
  ]
}
GET/api/ai-metrics/models-consolidatedBearer InventDB SOAR + InventDB Serverless

Per-model breakdown that, despite the name, reads THIS instance's own _system.AIUsage ledger (AIGateway.Usage is not written on an InventDB SOAR/customer instance). Raw (provider,model) variants are folded into one friendly model name; each row carries request count, token sums, average latency, total USD cost, and a 4-lever breakdown (Input / Cache read / Cache write / Output with tokens, per-1M rate, and subtotal). Sorted highest-spend first. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringWindow token: 24h/1d, 7d, 30d, 90d. When recognized it applies a WHERE timestamp >= threshold filter; otherwise all-time. Defaults to 30d.

Response 200

{
  "period": "30d",
  "models": [
    {
      "model": "Claude Opus 4.8",
      "requests": 210,
      "inputTokens": 980221,
      "outputTokens": 52110,
      "cachedTokens": 860003,
      "cacheWriteTokens": 42000,
      "avgLatencyMs": 2560,
      "totalCostUsd": 8.421,
      "breakdown": [
        {
          "lever": "Input",
          "tokens": 980221,
          "ratePerM": 15,
          "costUsd": 14.703315
        },
        {
          "lever": "Cache read",
          "tokens": 860003,
          "ratePerM": 1.5,
          "costUsd": 1.290005
        },
        {
          "lever": "Cache write",
          "tokens": 42000,
          "ratePerM": 18.75,
          "costUsd": 0.7875
        },
        {
          "lever": "Output",
          "tokens": 52110,
          "ratePerM": 75,
          "costUsd": 3.90825
        }
      ]
    }
  ]
}
GET/api/ai-metrics/quotasBearer InventDB SOAR + InventDB Serverless

Returns AI quota status from the quota subsystem. Admin/superadmin callers see full config (global + per-user limits), today's usage totals, and by-user / by-type breakdowns. Non-admins get a redacted view: global limit knobs and global totals are stripped, top-level quota-pct fields removed, byUser filtered to the caller's own row, and byType dropped. Read-only; no LLM, works on serverless.

Response 200

{
  "enabled": true,
  "date": "2026-07-13",
  "config": {
    "dailyRequestLimit": 6000,
    "dailyTokenLimit": 25000000,
    "perUserDailyRequestLimit": 600,
    "perUserDailyTokenLimit": 2500000
  },
  "usage": {
    "totalRequests": 2,
    "totalInputTokens": 112,
    "totalOutputTokens": 1582,
    "totalCachedTokens": 7354,
    "totalTokens": 1694
  },
  "requestQuotaPct": 0.0,
  "tokenQuotaPct": 0.0,
  "byUser": [
    {
      "user": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "requests": 2,
      "inputTokens": 112,
      "outputTokens": 1582,
      "cachedTokens": 7354,
      "totalTokens": 1694
    }
  ],
  "byType": [
    {
      "type": "ai_agent",
      "requests": 1,
      "tokens": 120
    },
    {
      "type": "ai_sql",
      "requests": 1,
      "tokens": 1574
    }
  ]
}
PUT/api/ai-metrics/quotasBearer InventDB SOAR + InventDB Serverless

Updates the global and per-user daily request/token limits and the enabled flag. Admin/superadmin only; returns 403 for any other role so a user with a domain write grant cannot raise their own AI limits. Returns the applied config. No LLM invoked; works on serverless.

Request

{
  "dailyRequestLimit": 6000,
  "dailyTokenLimit": 25000000,
  "perUserDailyRequestLimit": 600,
  "perUserDailyTokenLimit": 2500000,
  "enabled": true
}

Response 200

{
  "ok": true,
  "config": {
    "dailyRequestLimit": 6000,
    "dailyTokenLimit": 25000000,
    "perUserDailyRequestLimit": 600,
    "perUserDailyTokenLimit": 2500000,
    "enabled": true
  }
}
POST/api/ai-metrics/quotas/resetBearer InventDB SOAR + InventDB Serverless

Resets the current day's AI quota counter. Admin/superadmin only; returns 403 otherwise so a user cannot zero out their own usage to bypass the per-user cap. No LLM invoked; works on serverless.

Response 200

{
  "ok": true,
  "message": "Daily counter reset"
}
GET/api/ai-metrics/usage-by-userBearer InventDB SOAR + InventDB Serverless

Aggregates _system.AIUsage per database user in Rust: request count, input/output/cached tokens, total tokens (input+output) and average latency. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringLookback window token; defaults to 30d, echoed but not applied to filtering.

Response 200

{
  "period": "30d",
  "users": [
    {
      "user": "maya@pmco.example",
      "requests": 40,
      "inputTokens": 210000,
      "outputTokens": 10200,
      "cachedTokens": 180000,
      "totalTokens": 220200,
      "avgLatencyMs": 2100
    }
  ]
}
GET/api/ai-metrics/usage-by-typeBearer InventDB SOAR + InventDB Serverless

Aggregates _system.AIUsage per usageType (e.g. chat, report, extract) in Rust: request count, input/output/cached tokens, total tokens and average latency. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
periodquerystringLookback window token; defaults to 30d, echoed but not applied to filtering.

Response 200

{
  "period": "30d",
  "types": [
    {
      "type": "chat",
      "requests": 60,
      "inputTokens": 300110,
      "outputTokens": 15020,
      "cachedTokens": 260000,
      "totalTokens": 315130,
      "avgLatencyMs": 2200
    }
  ]
}
GET/api/ai-metrics/detailBearer InventDB SOAR + InventDB Serverless

Detailed analytics for THIS instance from _system.AIUsage (SELECT * LIMIT 200000, filtered by timestamp prefix in Rust). Default mode returns period totals (requests, tokens, cache hit rate, avg latency, success rate, dollars), a daily series, and byModel / byOperation / byUser / byProvider buckets, plus the current-month AI $ allowance from the cmgmt account cache (null on standalone instances). With ?day=YYYY-MM-DD it returns that day's per-request rows (newest first). With ?view=events it returns a filterable, paged event feed. Read-only reporting; no LLM, works on serverless.

Parameters

NameInTypeDescription
monthquerystringMonth window YYYY-MM. Defaults to the current UTC month.
dayquerystringDay drill-down YYYY-MM-DD; returns that day's per-request rows newest-first instead of the aggregate.
viewquerystringSet to 'events' for the month-scoped filterable/paged per-request event feed.
modelquerystringevents view: exact-match filter on friendly model name.
providerquerystringevents view: exact-match filter on friendly provider name.
userquerystringevents view: exact-match filter on database user.
opquerystringevents view: exact-match filter on usageType/operation.
pagequeryintegerevents view: zero-based page index. Defaults to 0.
sizequeryintegerevents view: page size, clamped 1..500. Defaults to 50.

Response 200

{
  "ok": true,
  "month": "2026-07",
  "totals": {
    "requests": 88,
    "inputTokens": 420110,
    "outputTokens": 21044,
    "cachedTokens": 360020,
    "cacheCreationTokens": 42000,
    "cacheHitRate": 0.4615,
    "avgLatencyMs": 2050,
    "successRate": 1,
    "dollars": 3.21
  },
  "daily": [
    {
      "day": "2026-07-12",
      "requests": 12,
      "inputTokens": 60110,
      "outputTokens": 3021,
      "cachedTokens": 50004
    }
  ],
  "byModel": [
    {
      "key": "Claude Opus 4.8",
      "requests": 60,
      "inputTokens": 300110,
      "outputTokens": 15020,
      "cachedTokens": 260000
    }
  ],
  "byOperation": [
    {
      "key": "chat",
      "requests": 60,
      "inputTokens": 300110,
      "outputTokens": 15020,
      "cachedTokens": 260000
    }
  ],
  "byUser": [
    {
      "key": "maya@pmco.example",
      "requests": 40,
      "inputTokens": 210000,
      "outputTokens": 10200,
      "cachedTokens": 180000
    }
  ],
  "byProvider": [
    {
      "key": "Anthropic",
      "requests": 60,
      "inputTokens": 300110,
      "outputTokens": 15020,
      "cachedTokens": 260000
    }
  ],
  "allowance": {
    "dollarsAllocated": 50,
    "dollarsUsed": 3.21,
    "periodMonth": "2026-07",
    "tierBase": "team"
  }
}
GET/api/ai-metrics/chorusBearer InventDB SOAR + InventDB Serverless

Computes the Chorus (semantic plan cache) funnel and measured savings entirely from _system.ChorusMetrics + _system.AIUsage with no hardcoded rates. Returns hit/agent-turn counts and hit-rate, the outcome funnel, chorus wall-time and priced extraction actuals (endpoint=chorus-extract), and for each observed default-model family a measured 'what those hits would have cost' comparison (avg agent-turn ms/tokens/cost plus time/tokens/cost saved). ChorusMetrics is absent until the first Chorus decision, degrading to zeros. Read-only reporting; no LLM, works on serverless.

Response 200

{
  "hits": 120,
  "agentTurns": 80,
  "hitRatePct": 60,
  "funnel": {
    "hit_sql": 90,
    "hit_tool": 30,
    "miss": 55,
    "skip": 25
  },
  "chorus": {
    "totalHitMs": 4200,
    "avgHitMs": 35,
    "extractCalls": 80,
    "extractTokens": 96000,
    "extractCostUsd": 0.144
  },
  "assumptions": {
    "callsPerAgentTurn": 3
  },
  "savingsByModel": [
    {
      "model": "Claude Opus 4.8",
      "avgAgentTurnMs": 7680,
      "avgAgentTurnTokens": 14400,
      "avgAgentTurnCostUsd": 0.216,
      "timeSavedMs": 917400,
      "tokensSaved": 1631000,
      "costSavedUsd": 25.776
    }
  ]
}

Saved Views Mixed tier

Persist a data-viewer card (its SQL + display config) as a reusable, listable Saved View, with deterministic custom-layout rendering and optional AI layout generation.

POST/api/saved-viewsBearer InventDB SOAR + InventDB Serverless

Persists a data-viewer card as a `_System.SavedViews` document: its authoritative `baseSql` plus display config (defaultMode, custom layout template link, attachment/search flags, column order, live browse filter, and the Ask-AI conversation that produced the SQL). `createdBy` is taken from the caller's JWT and `version` is forced to 1. Returns the stored document. Deterministic CRUD with no LLM, so it works on the InventDB Serverless tier.

Request

{
  "name": "Active Vacant Properties",
  "description": "Vacant units ordered by market rent",
  "namespace": "pms",
  "baseSql": "SELECT property_id, city, state, market_rent, status, beds, baths FROM pms.properties WHERE status = 'vacant' ORDER BY market_rent DESC",
  "defaultMode": "table",
  "attachmentsEnabled": false,
  "searchAttachments": false,
  "columns": [
    "property_id",
    "city",
    "state",
    "market_rent",
    "status"
  ],
  "search": "vacant",
  "sort": {
    "col": "market_rent",
    "dir": "desc"
  },
  "tags": [
    "properties",
    "vacancy"
  ]
}

Response 201

{
  "ok": true,
  "data": {
    "_id": "view_9f2a1c",
    "name": "Active Vacant Properties",
    "description": "Vacant units ordered by market rent",
    "namespace": "pms",
    "baseSql": "SELECT property_id, city, state, market_rent, status, beds, baths FROM pms.properties WHERE status = 'vacant' ORDER BY market_rent DESC",
    "defaultMode": "table",
    "customScope": "row",
    "attachmentsEnabled": false,
    "searchAttachments": false,
    "columns": [
      "property_id",
      "city",
      "state",
      "market_rent",
      "status"
    ],
    "search": "vacant",
    "sort": {
      "col": "market_rent",
      "dir": "desc"
    },
    "version": 1,
    "createdBy": "u_1001",
    "tags": [
      "properties",
      "vacancy"
    ]
  }
}
GET/api/saved-viewsBearer InventDB SOAR + InventDB Serverless

Lists all saved views (summary columns only: _id, name, description, namespace, defaultMode, attachmentsEnabled, version, createdBy) ordered by name, capped at 1000. Deterministic read with no LLM, so it works on the InventDB Serverless tier.

Response 200

{
  "ok": true,
  "data": {
    "views": [
      {
        "_id": "view_9f2a1c",
        "name": "Active Vacant Properties",
        "description": "Vacant units ordered by market rent",
        "namespace": "pms",
        "defaultMode": "table",
        "attachmentsEnabled": false,
        "version": 1,
        "createdBy": "u_1001"
      }
    ]
  }
}
GET/api/saved-views/{id}Bearer InventDB SOAR + InventDB Serverless

Fetches one saved view's full document by id. Returns 404 when the id does not exist. Deterministic read with no LLM, so it works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringThe saved view's _id (e.g. view_9f2a1c).

Response 200

{
  "ok": true,
  "data": {
    "name": "Active Vacant Properties",
    "description": "Vacant units ordered by market rent",
    "namespace": "pms",
    "baseSql": "SELECT property_id, city, state, market_rent, status, beds, baths FROM pms.properties WHERE status = 'vacant' ORDER BY market_rent DESC",
    "defaultMode": "table",
    "customScope": "row",
    "attachmentsEnabled": false,
    "searchAttachments": false,
    "columns": [
      "property_id",
      "city"
    ],
    "search": "vacant",
    "sort": {
      "col": "market_rent",
      "dir": "desc"
    },
    "version": 1,
    "createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
    "tags": [
      "properties",
      "vacancy"
    ],
    "_id": "5a1fcac6-7089-416a-aef3-57b7c73147ed",
    "_createdAt": "2026-07-13T15:57:13.185793800+00:00",
    "_updatedAt": "2026-07-13T15:57:13.185793800+00:00",
    "_createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee"
  }
}
PUT/api/saved-views/{id}Bearer InventDB SOAR + InventDB Serverless

Shallow-merges the JSON body into the existing saved-view document (any subset of fields), ignoring the immutable `_id`, `_createdAt`, and `createdBy` keys, and auto-increments `version`. Returns 404 if the view is missing. Deterministic mutation with no LLM, so it works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringThe saved view's _id to update.

Request

{
  "defaultMode": "custom",
  "customTemplateId": "tpl_7c33",
  "customScope": "row",
  "columns": [
    "property_id",
    "city",
    "market_rent",
    "beds",
    "baths"
  ]
}

Response 200

{
  "ok": true,
  "data": {
    "ok": true
  }
}
DELETE/api/saved-views/{id}Bearer InventDB SOAR + InventDB Serverless

Deletes the saved-view document by id. Returns the deleted id. Deterministic mutation with no LLM, so it works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
idpathstringThe saved view's _id to delete.

Response 200

{
  "ok": true,
  "data": {
    "deleted": "view_9f2a1c"
  }
}
POST/api/saved-views/render-layoutBearer InventDB SOAR + InventDB Serverless

Renders a view's custom (non-table) card layout for a single page. Windows `baseSql` server-side via the engine parser (no client SQL surgery), loads the linked `_System.ReportTemplates` template, runs it over that page with `params.viewSql` set to the windowed SQL, and returns `{ html, total, page, pageSize }` where `total` is the real full row count for accurate paging. Pure report-engine rendering with no model in the path, so it works on the InventDB Serverless tier (an AI-authored template renders with no LLM).

Request

{
  "templateId": "tpl_7c33",
  "baseSql": "SELECT property_id, city, state, market_rent, status, beds, baths FROM pms.properties WHERE status = 'vacant' ORDER BY market_rent DESC",
  "page": 0,
  "pageSize": 25
}

Response 200

{
  "ok": true,
  "data": {
    "html": "<section class=\"cards\">…<div class=\"card\" data-record-id=\"P-5001\">…</div>…</section>",
    "total": 128,
    "page": 0,
    "pageSize": 25
  }
}
POST/api/saved-views/generate-layoutBearer InventDB SOAR only

Invokes the built-in LLM to produce report-engine HTML for a view's custom (non-table) card layout. Fresh generation designs a card layout from the schema + sample records; when `currentHtml` is supplied it EDITS that design applying only `instruction` (with `history` as context). Attached `pendingIds` (from /ai/uploads/stage) are passed as vision inputs (images, e.g. a mockup) or inlined text. The HTML reads its rows from `query(params.viewSql)` and may embed a leading `<!--VIEWSQL: SELECT … -->` directive; the response returns `{ html, sql }` (sql falls back to baseSql). This route calls the built-in LLM, so it returns HTTP 403 on the InventDB Serverless tier. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "baseSql": "SELECT property_id, city, state, market_rent, status, beds, baths, sqft FROM pms.properties",
  "namespace": "pms",
  "instruction": "Property cards showing photo, city, beds/baths and rent, highest sqft first",
  "modelFamily": "claude"
}

Response 200

{
  "html": "<style>…</style><script type=\"server\">const r = query(params.viewSql); for (const row of r.rows) { out.write(`<div class=\"card\" data-surface=\"flat\" data-record-id=\"${esc(String(row._id))}\">…${esc(row.city)}…${fmt.currency(row.market_rent)}…</div>`); }</script>",
  "sql": "SELECT * FROM pms.properties ORDER BY sqft DESC"
}

Forms Any tier

Schema-driven data-entry form engine: analyze a type's schema, build/store HTML form templates, render forms with FK typeahead lookups, run SQL validation rules, and submit records (create/update).

GET/forms/schema/{namespace}/{type_name}Bearer InventDB SOAR + InventDB Serverless

Samples records from the given namespace/type (default 100 rows) to infer each property's data type, FK targets (fields ending in _id whose target type exists), FK display fields, cascade relationships (e.g. unit_id cascades from property_id), and enum-like fields (2-20 distinct values). Returns the inferred property list, the type's display field, and total record count. Used by an external AI agent to design a form template; the route itself runs no LLM.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'
type_namepathstringType (table) name, e.g. 'properties'
sample_sizequeryintegerNumber of records to sample for schema inference (default 100)

Response 200

{
  "ok": true,
  "namespace": "pms",
  "typeName": "properties",
  "properties": [
    {
      "name": "owner_id",
      "type": "string",
      "is_fk": true,
      "fk_target": "owners",
      "fk_display": "name"
    },
    {
      "name": "beds",
      "type": "integer"
    },
    {
      "name": "city",
      "type": "string"
    },
    {
      "name": "status",
      "type": "string",
      "enum_values": [
        "occupied",
        "vacant"
      ]
    },
    {
      "name": "market_rent",
      "type": "number"
    }
  ],
  "displayField": "_id",
  "recordCount": 5000
}
GET/forms/lookup/{namespace}/{type_name}Bearer InventDB SOAR + InventDB Serverless

Searches a target type for a searchable FK picker. Matches the query against the detected display field(s) and _id via LIKE, optionally constrained by a cascade filter (filter=field:value). Returns up to 'limit' items (default 20, capped 200), each with _id, a composed display string, and the full record. No LLM involved.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'
type_namepathstringTarget type to search, e.g. 'owners'
qquerystringSearch text matched against display field(s) and _id
filterquerystringCascade filter in the form field:value, e.g. 'property_id:P-5001'
limitqueryintegerMax results (default 20, capped at 200)

Response 200

{
  "ok": true,
  "items": [
    {
      "_id": "P-5019",
      "display": "P-5019",
      "record": {
        "property_id": "P-5001",
        "city": "Richmond"
      }
    },
    {
      "_id": "P-5037",
      "display": "P-5037",
      "record": {
        "property_id": "P-5037",
        "owner_id": "O-1005",
        "street": "3924 Pinecrest Cir",
        "unit": "Apt 301",
        "city": "Falls Church",
        "state": "VA",
        "zip": "22042",
        "region": "Northern VA",
        "type": "Apartment Unit",
        "beds": 1,
        "baths": 1,
        "sqft": 968,
        "year_built": 2015,
        "lot_ac": 0,
        "garage": "None",
        "market_rent": 2550,
        "market_value": 468000,
        "acq_date": "2017-10-22T12:00:00",
        "acq_price": 227000,
        "annual_tax": 4000,
        "annual_insurance": 1025,
        "monthly_hoa": 165,
        "status": "Occupied",
        "_id": "P-5037",
        "_createdAt": "2026-07-13T15:51:56.948069100+00:00",
        "_updatedAt": "2026-07-13T15:51:56.948069100+00:00"
      }
    }
  ],
  "total": 20
}
GET/forms/templatesBearer InventDB SOAR + InventDB Serverless

Returns up to 200 form template documents stored in _System.FormTemplates. Pure read; no LLM.

Response 200

{
  "ok": true,
  "data": [
    {
      "_id": "pm.property",
      "namespace": "pm",
      "typeName": "property",
      "fields": [
        {
          "name": "property_id",
          "order": 0
        },
        {
          "name": "owner_id",
          "order": 1
        }
      ],
      "_createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "_createdAt": "2026-06-12T07:05:29.806022100+00:00",
      "_updatedAt": "2026-06-12T07:05:29.806022100+00:00"
    },
    {
      "_id": "crm.departments",
      "namespace": "crm",
      "typeName": "departments",
      "fields": [
        {
          "name": "department_code",
          "order": 0,
          "label": "Department Code",
          "type": "text"
        },
        {
          "name": "name",
          "order": 1,
          "label": "Department Name",
          "type": "text"
        }
      ],
      "_createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "_createdAt": "2026-06-12T11:23:15.626362500+00:00",
      "_updatedAt": "2026-06-12T11:23:15.626362500+00:00"
    }
  ]
}
POST/forms/templatesBearer InventDB SOAR + InventDB Serverless

Inserts a new form template into _System.FormTemplates with the supplied name, target namespace/type, HTML template body (with {{field:*}}, {{lookup:*}}, {{validate:*}} placeholders), bindings, and style. Stamps status='active', version=1, and created_by from the caller's claims. The template body is typically authored by an external AI agent; this route runs no LLM. Returns 201 with the new record id.

Request

{
  "name": "New Property",
  "namespace": "pms",
  "typeName": "properties",
  "htmlTemplate": "{{field:city|label=City|required}} {{lookup:owner_id|target=owners|display=name}}",
  "bindings": [],
  "style": {
    "primaryColor": "#0d9488"
  }
}

Response 201

{
  "ok": true,
  "id": "FT-02"
}
GET/forms/templates/{id}Bearer InventDB SOAR + InventDB Serverless

Returns a single form template document from _System.FormTemplates, or ok:false if not found. Pure read; no LLM.

Parameters

NameInTypeDescription
idpathstringForm template record id

Response 200

{
  "ok": true,
  "data": {
    "_id": "FT-02",
    "name": "New Property",
    "namespace": "pms",
    "typeName": "properties",
    "htmlTemplate": "{{field:city|label=City}}",
    "status": "active",
    "version": 1
  }
}
PUT/forms/templates/{id}Bearer InventDB SOAR + InventDB Serverless

Applies the supplied JSON as a partial update to the form template document in _System.FormTemplates. Accepts an arbitrary object body. No LLM.

Parameters

NameInTypeDescription
idpathstringForm template record id

Request

{
  "name": "New Property (v2)",
  "style": {
    "primaryColor": "#48176a"
  }
}

Response 200

{
  "ok": true
}
GET/forms/{id}/renderBearer InventDB SOAR + InventDB Serverless

Loads the template, resolves {{field}}, {{lookup}}, {{section}}, {{row}}, {{hidden}}, {{validate}} placeholders into inputs, and returns a self-contained HTML page (embedded CSS + FormEngine JS) for creating a new record. Content-Type text/html. No LLM.

Parameters

NameInTypeDescription
idpathstringForm template record id

Response 200

<!DOCTYPE html><html lang="en">... New Property form ...</html>
GET/forms/{id}/render/{record_id}Bearer InventDB SOAR + InventDB Serverless

Same as render but loads the target record (namespace/type from the template) and pre-fills inputs and lookup chips for editing. Submit label becomes 'Save Changes'. Returns a self-contained HTML page. No LLM.

Parameters

NameInTypeDescription
idpathstringForm template record id
record_idpathstringId of the record to edit, e.g. 'P-5001'

Response 200

<!DOCTYPE html><html lang="en">... Editing properties record P-5001 ...</html>
POST/forms/validateBearer InventDB SOAR + InventDB Serverless

Substitutes :param placeholders in the supplied SQL with values from params (single-quote escaped), executes it against the database, and returns the resulting rows and count. Used by the form engine to enforce {{validate:*}} rules before submit. Executes SQL only; no LLM.

Request

{
  "sql": "SELECT COUNT(*) AS c FROM pms.properties WHERE property_id = :property_id",
  "params": {
    "property_id": "P-5001"
  }
}

Response 200

{
  "ok": true,
  "rows": [
    {
      "c": 1
    }
  ],
  "count": 1
}
POST/forms/{id}/submitBearer InventDB SOAR + InventDB Serverless

Resolves the template's target namespace/type, stamps created_by and modified_by from the caller's claims, and inserts the submitted data as a new record. Returns 201 with the new id. No LLM.

Parameters

NameInTypeDescription
idpathstringForm template record id

Request

{
  "property_id": "P-5001",
  "owner_id": "O-1001",
  "city": "Austin",
  "state": "TX",
  "market_rent": 2450,
  "status": "vacant",
  "beds": 3,
  "baths": 2,
  "sqft": 1650,
  "market_value": 412000
}

Response 201

{
  "ok": true,
  "id": "P-5001"
}
PUT/forms/{id}/submit/{record_id}Bearer InventDB SOAR + InventDB Serverless

Resolves the template's target namespace/type, stamps modified_by from the caller's claims, and updates the identified record with the submitted data. Returns 200 with the record id. No LLM.

Parameters

NameInTypeDescription
idpathstringForm template record id
record_idpathstringId of the record to update, e.g. 'P-5001'

Request

{
  "market_rent": 2600,
  "status": "occupied"
}

Response 200

{
  "ok": true,
  "id": "P-5001"
}

Transactions Any tier

Multi-statement ACID transaction lifecycle: begin, execute buffered CRUD/SQL operations, then commit or rollback.

POST/txBearer InventDB SOAR + InventDB Serverless

Opens a new server-side transaction and returns its generated transaction id. Subsequent execute/commit/rollback calls reference this id. No LLM involved.

Response 201

{
  "ok": true,
  "txId": "tx-8f3c2a10-4b7e-4d21-9a55-1c0e2f9b6d84"
}
POST/tx/{txId}/commitBearer InventDB SOAR + InventDB Serverless

Durably commits all buffered operations for the given transaction id. Returns 200 on success or 400 with an error message if the commit fails (e.g. conflict or unknown/expired txId). No LLM involved.

Parameters

NameInTypeDescription
txIdpathstringTransaction id returned by POST /tx

Response 200

{
  "ok": true
}
POST/tx/{txId}/rollbackBearer InventDB SOAR + InventDB Serverless

Discards all buffered operations for the given transaction id and releases it. Always returns 200 ok (rollback is idempotent/best-effort). No LLM involved.

Parameters

NameInTypeDescription
txIdpathstringTransaction id returned by POST /tx

Response 200

{
  "ok": true
}
POST/tx/{txId}/executeBearer InventDB SOAR + InventDB Serverless

Runs a single operation within the open transaction. The request body is tagged by an 'operation' discriminator: insert, update, delete, get, or query. insert/update run referential-integrity validation (returns 409 on RI violation); delete runs RI-for-delete validation (409 on violation). update/delete/get return 404 when the target document does not exist. query runs SQL within the transaction and returns the result rows array. Response carries ExecuteResponse { ok, data, id, error }; success uses HTTP 201 for insert and 200 for update/delete/get/query. No LLM involved; plain buffered CRUD/SQL.

Parameters

NameInTypeDescription
txIdpathstringTransaction id returned by POST /tx

Request

{
  "operation": "insert",
  "namespace": "pms",
  "type": "properties",
  "document": {
    "property_id": "P-5001",
    "owner_id": "O-1001",
    "city": "Austin",
    "state": "TX",
    "market_rent": 2450,
    "status": "occupied",
    "beds": 3,
    "baths": 2,
    "sqft": 1680,
    "market_value": 415000
  }
}

Response 201

{
  "ok": true,
  "id": "01J8ZC7K3QET9F2M0PROP5001",
  "data": {
    "_id": "01J8ZC7K3QET9F2M0PROP5001",
    "property_id": "P-5001",
    "owner_id": "O-1001",
    "city": "Austin",
    "state": "TX",
    "market_rent": 2450,
    "status": "occupied",
    "beds": 3,
    "baths": 2,
    "sqft": 1680,
    "market_value": 415000
  }
}

Account Any tier

Instance-native account card: who the caller is plus this instance's company, plan tier, and live AI-dollar allowance in one call.

GET/api/account/summaryBearer InventDB SOAR + InventDB Serverless

Returns, in a single call, the calling user's identity plus the company, plan tier, and live AI-dollar allowance for THIS instance so the InventDB SOAR profile card and the Console settings page can show company/tier/credits without deep-linking back to the customer portal. The 'user' block is resolved from the caller's own _system.users row (self-introspection pinned to _id = JWT subject under a system principal), falling back to claims-only data for service-account subjects with no row. The 'customer' and 'allowance' blocks come from the cmgmt_account poller cache (HMAC-signed hourly pull from cmgmt); both serialize as null on a STANDALONE instance with no cmgmt configured, and the frontends degrade to user-only. NOT AI-gated: serverless instances still surface user + company + plan (the allowance just stays null/empty there since serverless has no built-in LLM budget). Auth-protected by the global AuthMiddleware (Bearer JWT).

Response 200

{
  "user": {
    "id": "usr_9c1f2a",
    "username": "asha.pm",
    "email": "asha.pm@pms.example.com",
    "role": "user"
  },
  "customer": {
    "name": "Copperline Property Management",
    "tierCode": "ai-platform",
    "tierProduct": "AI Platform",
    "planStatus": "active",
    "renewsAt": "2026-08-01T00:00:00Z"
  },
  "allowance": {
    "dollarsAllocated": 50,
    "dollarsUsed": 12.47,
    "periodMonth": "2026-07",
    "tierBase": 50
  }
}

Users, Roles & Permissions Any tier

Admin CRUD for users, per-user/role permissions (RBAC grants + row-level filters), and named/app-scoped roles.

GET/api/usersBearer InventDB SOAR + InventDB Serverless

List all users from _system.users. Admin only. Returns a flat JSON array of user objects directly (C#-API compatible, without a wrapper). Supports optional activeOnly and search (username/email substring, case-insensitive) query filters.

Parameters

NameInTypeDescription
activeOnlyquerybooleanWhen true, only users with is_active=true are returned.
searchquerystringCase-insensitive substring match on username or email.

Response 200

[
  {
    "_id": "1f5387b6-487d-44e1-a869-c48f97e06b39",
    "username": "manager1",
    "email": "manager1@example.com",
    "role": "writer",
    "isActive": true,
    "createdAt": "2026-07-13T15:24:18.648185700+00:00",
    "updatedAt": "2026-07-13T15:24:18.648185700+00:00"
  },
  {
    "_id": "3ebe362f-79eb-4726-803c-70d56c36a2e8",
    "username": "O-1001",
    "email": null,
    "role": "rbac_owner_rw",
    "isActive": true,
    "createdAt": "2026-07-04T13:15:54.528755300+00:00",
    "updatedAt": "2026-07-04T13:15:54.528755300+00:00"
  }
]
POST/api/usersBearer InventDB SOAR + InventDB Serverless

Create a new user in _system.users. Admin only. Hashes the password, generates a UUID _id, defaults role to 'reader'. 409 if the username already exists; 400 if username is empty or >64 chars. Returns the created user wrapped in {ok,user}.

Request

{
  "username": "manager1",
  "password": "S3cret!pass",
  "email": "manager1@example.com",
  "role": "writer"
}

Response 201

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0009",
    "username": "manager1",
    "email": "manager1@example.com",
    "role": "writer",
    "isActive": true,
    "createdAt": "2026-06-23T10:00:00Z",
    "updatedAt": "2026-06-23T10:00:00Z"
  }
}
GET/api/users/{userId}Bearer InventDB SOAR + InventDB Serverless

Fetch one user by _id, or by username as a fallback (the Console passes the username). Admin only. Returns {ok,user}; 404 when neither _id nor username matches.

Parameters

NameInTypeDescription
userIdpathstringUser _id or username.

Response 200

{
  "ok": true,
  "user": {
    "_id": "1b37c660-8a99-4f08-a6ed-ae77d7948960",
    "username": "manager1",
    "email": "manager1@example.com",
    "role": "writer",
    "isActive": true,
    "createdAt": "2026-07-13T16:08:46.428791800+00:00",
    "updatedAt": "2026-07-13T16:08:46.428791800+00:00"
  }
}
PUT/api/users/{userId}Bearer InventDB SOAR + InventDB Serverless

Update a user's email, role, and/or password (any subset). Admin only. Password is re-hashed if supplied. Returns the updated user in {ok,user}; 404 if not found.

Parameters

NameInTypeDescription
userIdpathstringUser _id.

Request

{
  "email": "newmail@example.com",
  "role": "analyst"
}

Response 200

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0002",
    "username": "O-1001",
    "email": "newmail@example.com",
    "role": "analyst",
    "isActive": true,
    "createdAt": "2026-05-02T09:00:00Z",
    "updatedAt": "2026-06-23T10:05:00Z"
  }
}
DELETE/api/users/{userId}Bearer InventDB SOAR + InventDB Serverless

Delete a user by _id. Admin only. Refuses to delete the caller's own account (400). Returns {ok:true}; 404 if not found.

Parameters

NameInTypeDescription
userIdpathstringUser _id to delete.

Response 200

{
  "ok": true
}
PUT/api/users/{userId}/activateBearer InventDB SOAR + InventDB Serverless

Set is_active=true on the user. Admin only. Returns the updated user in {ok,user}; 404 if not found.

Parameters

NameInTypeDescription
userIdpathstringUser _id.

Response 200

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0002",
    "username": "O-1001",
    "email": "owner@example.com",
    "role": "reader",
    "isActive": true,
    "createdAt": "2026-05-02T09:00:00Z",
    "updatedAt": "2026-06-23T10:06:00Z"
  }
}
PUT/api/users/{userId}/deactivateBearer InventDB SOAR + InventDB Serverless

Set is_active=false on the user. Admin only. Returns the updated user in {ok,user}; 404 if not found.

Parameters

NameInTypeDescription
userIdpathstringUser _id.

Response 200

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0002",
    "username": "O-1001",
    "email": "owner@example.com",
    "role": "reader",
    "isActive": false,
    "createdAt": "2026-05-02T09:00:00Z",
    "updatedAt": "2026-06-23T10:07:00Z"
  }
}
PUT/api/users/{userId}/roleBearer InventDB SOAR + InventDB Serverless

Change the user's global (JWT primary) role. Admin only. Valid roles: reader, writer, analyst, admin, superadmin. Only a superadmin may grant the superadmin role (403 otherwise); invalid role => 400. Returns {ok,user}.

Parameters

NameInTypeDescription
userIdpathstringUser _id.

Request

{
  "role": "analyst"
}

Response 200

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0002",
    "username": "O-1001",
    "email": "owner@example.com",
    "role": "analyst",
    "isActive": true,
    "createdAt": "2026-05-02T09:00:00Z",
    "updatedAt": "2026-06-23T10:08:00Z"
  }
}
PUT/api/users/{userId}/app-roles/{app}Bearer InventDB SOAR + InventDB Serverless

Set app_roles[<app>]=<role> on a user (dynamic-roles design). Admin only. Validates that (app, role) exists in _system.roles; '_global' is rejected as an app (400). Returns {ok,user}; 404 if the user is not found.

Parameters

NameInTypeDescription
userIdpathstringUser _id.
apppathstringApp id whose role catalog the role belongs to (e.g. inventdb-tenantry).

Request

{
  "role": "Manager"
}

Response 200

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0002",
    "username": "O-1001",
    "email": "owner@example.com",
    "role": "reader",
    "isActive": true,
    "createdAt": "2026-05-02T09:00:00Z",
    "updatedAt": "2026-06-23T10:09:00Z"
  }
}
DELETE/api/users/{userId}/app-roles/{app}Bearer InventDB SOAR + InventDB Serverless

Remove app_roles[<app>] from a user. Admin only. Returns {ok,user}; 404 if the user is not found.

Parameters

NameInTypeDescription
userIdpathstringUser _id.
apppathstringApp id to unassign.

Response 200

{
  "ok": true,
  "user": {
    "_id": "a1b2c3d4-0002",
    "username": "O-1001",
    "email": "owner@example.com",
    "role": "reader",
    "isActive": true,
    "createdAt": "2026-05-02T09:00:00Z",
    "updatedAt": "2026-06-23T10:10:00Z"
  }
}
GET/api/permissions/checkBearer InventDB SOAR + InventDB Serverless

Check whether the CURRENT user has a given access level on a resource. Admin/superadmin short-circuit to has_permission=true. Delegates to the same rbac helpers the enforcement middleware uses (direct grants + role grants, case-insensitive, access-level hierarchy) so the UI never shows buttons that 403 on click. resource is either 'ns' or 'ns.type'.

Parameters

NameInTypeDescription
resourcequerystringNamespace ('pms') or fully-qualified type ('pms.properties').
levelquerystringAccess level to test: read|write|delete|admin (case-insensitive).

Response 200

{
  "ok": true,
  "has_permission": true
}
GET/api/permissions/{username}Bearer InventDB SOAR + InventDB Serverless

Return the flat array of direct permission grants for a user (Archivus-compatible shape). Admin only. Accepts username or _id in the path; storage is keyed by resolved _id. Enriches each grant with grantedByUsername (batched _id->username lookup). Returns [] when the user has no grants.

Parameters

NameInTypeDescription
usernamepathstringUsername or _id of the subject user.

Response 200

[
  {
    "_id": "perm-uuid-1",
    "scope": "type",
    "resource": "pms.properties",
    "accessLevel": "read",
    "grantedBy": "a1b2c3d4-0001",
    "grantedByUsername": "admin",
    "grantedAt": "2026-06-01T12:00:00Z",
    "rowFilter": "_createdBy = :current_user"
  }
]
POST/api/permissions/{username}Bearer InventDB SOAR + InventDB Serverless

Grant a direct permission to a user. Admin only. accessLevel must be read|write|admin (400 otherwise); target user must exist (404, except the 'superadmin' pseudo-user). Optional rowFilter (v1 DSL) and rowFilterSql (v2 SQL) scope the grant to a row subset. Returns 201 with the created permission.

Parameters

NameInTypeDescription
usernamepathstringUsername or _id of the subject user.

Request

{
  "scope": "type",
  "resource": "pms.properties",
  "accessLevel": "read",
  "rowFilter": "owner_id = :current_user"
}

Response 201

{
  "_id": "perm-uuid-2",
  "scope": "type",
  "resource": "pms.properties",
  "accessLevel": "read",
  "grantedBy": "a1b2c3d4-0001",
  "grantedByUsername": "admin",
  "grantedAt": "2026-06-23T10:12:00Z",
  "rowFilter": "owner_id = :current_user"
}
DELETE/api/permissions/{username}/{permissionId}Bearer InventDB SOAR + InventDB Serverless

Revoke a single grant from a user by permission _id. Admin only. 404 if the user has no permissions doc or the permissionId is not present. Returns 204 No Content on success.

Parameters

NameInTypeDescription
usernamepathstringUsername or _id of the subject user.
permissionIdpathstringThe _id of the grant to revoke.

Response 204

GET/api/rolesBearer InventDB SOAR + InventDB Serverless

List all role records from _system.roles (legacy bundle-named + app-scoped). Admin only. Returns {ok:true,data:[Role]}.

Response 200

{
  "ok": true,
  "data": [
    {
      "_id": "leasing_agent",
      "name": "leasing_agent",
      "description": "Read/write on leases, read-only on all other pms data",
      "grants": [
        {
          "scope": "type",
          "resource": "pms.leases",
          "accessLevel": "write"
        },
        {
          "scope": "namespace",
          "resource": "pms",
          "accessLevel": "read"
        }
      ],
      "system": false,
      "owner": "user",
      "createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "createdAt": "2026-06-14T12:26:13.963286700+00:00"
    },
    {
      "_id": "pms:owner",
      "name": "pms:owner",
      "description": "Owner sees only their own properties",
      "grants": [
        {
          "scope": "type",
          "resource": "pms.properties",
          "accessLevel": "read",
          "rowFilter": "owner_id = :current_user"
        }
      ],
      "system": false,
      "owner": "user",
      "createdBy": "7d4e1a90-52c1-4f38-949b-0decafc0ffee",
      "createdAt": "2026-07-13T15:24:18.734618100+00:00"
    }
  ]
}
POST/api/rolesBearer InventDB SOAR + InventDB Serverless

Create a legacy bundle-named role (_id = name; users opt in via roles[]). Admin only. Validates the name (1-64 chars, [A-Za-z0-9_-:], and that it is not a reserved name), every grant, and any rowFilter. 409 if the name already exists or is bundle-owned. Returns {ok:true,data:Role}.

Request

{
  "name": "pms:owner",
  "description": "Owner sees only their own properties",
  "grants": [
    {
      "scope": "type",
      "resource": "pms.properties",
      "accessLevel": "read",
      "rowFilter": "owner_id = :current_user"
    }
  ]
}

Response 200

{
  "ok": true,
  "data": {
    "_id": "pms:owner",
    "name": "pms:owner",
    "description": "Owner sees only their own properties",
    "grants": [
      {
        "scope": "type",
        "resource": "pms.properties",
        "accessLevel": "read",
        "rowFilter": "owner_id = :current_user"
      }
    ],
    "system": false,
    "owner": "user",
    "createdBy": "a1b2c3d4-0001",
    "createdAt": "2026-06-23T10:13:00Z"
  }
}
GET/api/roles/{name}Bearer InventDB SOAR + InventDB Serverless

Fetch one role by bare name (e.g. 'analyst', resolves the seeded role__global_<name> doc) or by fully-qualified internal _id. Admin only. Returns {ok:true,data:Role}; 404 if not found.

Parameters

NameInTypeDescription
namepathstringRole name or internal role _id.

Response 200

{
  "ok": true,
  "data": {
    "_id": "role__global_analyst",
    "name": "analyst",
    "app": "_global",
    "description": "Analyst - read-only + own reports.",
    "grants": [],
    "system": true,
    "owner": "system"
  }
}
PUT/api/roles/{name}Bearer InventDB SOAR + InventDB Serverless

Update a user-owned legacy role's description, grants, and/or rowFilter (rowFilter accepts null to clear). Admin only. Refuses built-in roles (403) and bundle-owned roles (409). Validates grants/rowFilter. Returns {ok:true,data:Role}.

Parameters

NameInTypeDescription
namepathstringRole _id/name to update.

Request

{
  "description": "Owner read scope (updated)",
  "grants": [
    {
      "scope": "type",
      "resource": "pms.properties",
      "accessLevel": "read",
      "rowFilter": "owner_id = :current_user"
    }
  ]
}

Response 200

{
  "ok": true,
  "data": {
    "_id": "pms:owner",
    "name": "pms:owner",
    "description": "Owner read scope (updated)",
    "grants": [
      {
        "scope": "type",
        "resource": "pms.properties",
        "accessLevel": "read",
        "rowFilter": "owner_id = :current_user"
      }
    ],
    "system": false,
    "owner": "user",
    "updatedAt": "2026-06-23T10:14:00Z"
  }
}
DELETE/api/roles/{name}Bearer InventDB SOAR + InventDB Serverless

Delete a user-owned legacy role. Admin only. Refuses built-in roles (403) and bundle-owned roles (409). By default 409s if any users still carry the role in roles[] (lists them); pass ?force=true to auto-detach from all users first. Returns {ok:true}.

Parameters

NameInTypeDescription
namepathstringRole _id/name to delete.
forcequerybooleanWhen true, detach the role from every user first, then delete.

Response 200

{
  "ok": true
}
POST/api/roles/{name}/bulk-attachBearer InventDB SOAR + InventDB Serverless

Attach one role to many users in a single admin call (idempotent). Provide exactly one of userIds (explicit _id list) or whereSql (a WHERE-clause body run as SELECT _id FROM _system.users WHERE <whereSql> under a system principal). 404 if the role does not exist; 400 if neither/both selectors given. Returns counts of attached/skipped plus per-user errors.

Parameters

NameInTypeDescription
namepathstringRole name to attach.

Request

{
  "whereSql": "username LIKE 'O-%'"
}

Response 200

{
  "ok": true,
  "attached": 42,
  "skipped": 3,
  "role": "pms:owner"
}
POST/api/users/{username}/rolesBearer InventDB SOAR + InventDB Serverless

Attach a single legacy role to one user (adds to roles[], idempotent). Admin only. Accepts username or _id in the path. 404 if the role or user is not found. Returns {ok:true,data:{username,roles}}.

Parameters

NameInTypeDescription
usernamepathstringUsername or _id of the target user.

Request

{
  "role": "pms:owner"
}

Response 200

{
  "ok": true,
  "data": {
    "username": "O-1001",
    "roles": [
      "pms:owner"
    ]
  }
}
DELETE/api/users/{username}/roles/{role}Bearer InventDB SOAR + InventDB Serverless

Remove a single legacy role from a user's roles[] array. Admin only. Accepts username or _id in the path; no-op if the user or role is absent. Returns {ok:true}.

Parameters

NameInTypeDescription
usernamepathstringUsername or _id of the target user.
rolepathstringRole name to detach.

Response 200

{
  "ok": true
}

Vectors (per-property) Any tier

Admin-only management of per-property and per-segment (universal HNSW) vector embeddings: inspect, (re)generate, backfill, and delete MiniLM embeddings.

GET/api/vectors/{namespace}/{type}Bearer InventDB SOAR + InventDB Serverless

Returns every property that has a vector (distinct-value) embedding index for the given namespace/type, plus per-property vector count, source property, embedding dimension (384 for all-MiniLM-L6-v2), on-disk file size, and last rebuild time. Also returns the type's total record count. Admin/superadmin only (403 otherwise). Vector embeddings use the local MiniLM model rather than the built-in chat LLM, so this works on the InventDB Serverless tier.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.

Response 200

{
  "namespace": "pms",
  "type": "properties",
  "totalRecords": 500,
  "vectorProperties": [
    {
      "propertyName": "city",
      "vectorCount": 42,
      "sourceProperty": "city",
      "dimension": 384,
      "fileSizeBytes": 65536,
      "lastRebuild": "2026-06-20T14:03:11+00:00"
    }
  ]
}
GET/api/vectors/{namespace}/{type}/available-propertiesBearer InventDB SOAR + InventDB Serverless

Samples one document from the type and returns every non-system string property (skips names starting with '_', ending in 'Id', or equal to 'id') as a candidate for vectorization, flagging which ones already have an embedding index and their current embedding count. Also returns the total record count. Admin/superadmin only (403 otherwise). Returns 404 if the type does not exist. No built-in LLM involved.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.

Response 200

{
  "namespace": "pms",
  "type": "properties",
  "totalRecords": 500,
  "stringProperties": [
    {
      "propertyName": "city",
      "hasEmbedding": true,
      "embeddingCount": 42
    },
    {
      "propertyName": "state",
      "hasEmbedding": false,
      "embeddingCount": 0
    },
    {
      "propertyName": "status",
      "hasEmbedding": false,
      "embeddingCount": 0
    }
  ]
}
GET/api/vectors/{namespace}/{type}/{property}/regenerateBearer InventDB SOAR + InventDB Serverless

Builds the distinct-value MiniLM embedding cache + HNSW index for a single property and streams progress as Server-Sent Events. Append-only (incremental) by default; pass ?force=true to wipe and rebuild from scratch. Accepts an optional ?token query param so an EventSource (which cannot set Authorization headers) can authenticate. Each SSE 'message' event carries a VectorRegenerationProgress JSON payload (status regenerating/completed/failed, totalRecords=distinct count, processedRecords=cached count, progressPercentage, timestamps, elapsedMs); a final 'done' event closes the stream. Admin/superadmin only (a non-admin caller receives a failed progress event rather than an HTTP 403). Uses the local MiniLM embedder rather than the built-in chat LLM.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.
propertypathstringProperty to embed, e.g. 'city'.
tokenquerystringOptional bearer token for EventSource auth (used in place of the Authorization header).
forcequerybooleanIf true, wipe the existing cache and rebuild from scratch. Default false (incremental append-only build).

Response 200

event: message
data: {"namespace":"pms","type":"properties","propertyName":"city","status":"regenerating","totalRecords":42,"processedRecords":0,"progressPercentage":0.0,"startTime":"2026-06-20T14:03:11+00:00","elapsedMs":0}

event: message
data: {"namespace":"pms","type":"properties","propertyName":"city","status":"completed","totalRecords":42,"processedRecords":42,"progressPercentage":100.0,"startTime":"2026-06-20T14:03:11+00:00","endTime":"2026-06-20T14:03:12+00:00","elapsedMs":840}

event: done
data: {}

GET/api/vectors/{namespace}/{type}/{property}/progressBearer InventDB SOAR + InventDB Serverless

Returns the last-known VectorRegenerationProgress for a property's embedding build from the in-memory progress tracker; if no build has been tracked it returns an 'idle' snapshot. Used to poll batch regenerations that don't stream SSE. Always HTTP 200 (a non-admin caller gets status 'error' with error_message 'Admin access required' in the body rather than an HTTP 403). No built-in LLM involved.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.
propertypathstringProperty whose build progress is queried, e.g. 'city'.

Response 200

{
  "namespace": "pms",
  "type": "properties",
  "propertyName": "city",
  "status": "completed",
  "totalRecords": 42,
  "processedRecords": 42,
  "progressPercentage": 100,
  "startTime": "2026-06-20T14:03:11+00:00",
  "endTime": "2026-06-20T14:03:12+00:00",
  "elapsedMs": 840
}
DELETE/api/vectors/{namespace}/{type}/{property}Bearer InventDB SOAR + InventDB Serverless

Deletes the distinct-value embedding cache file for a single property. Returns ok=true with vectorsDeleted=1 when a cache existed, ok=true with vectorsDeleted=0 when none existed, and 500 on I/O failure. Admin/superadmin only (403 otherwise). No built-in LLM involved.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.
propertypathstringProperty whose embedding cache to delete, e.g. 'city'.

Response 200

{
  "ok": true,
  "message": "Deleted distinct embedding cache for pms.properties.city",
  "vectorsDeleted": 1
}
POST/api/vectors/{namespace}/{type}/regenerate-batchBearer InventDB SOAR + InventDB Serverless

Queues incremental (append-only) embedding rebuilds for a list of properties, spawning a background build per property (skips any already regenerating). Returns immediately with the list of queued properties; poll the per-property /progress endpoint to track completion. Returns 400 if the properties list is empty. Admin/superadmin only (403 otherwise). Uses the local MiniLM embedder rather than the built-in chat LLM.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.

Request

{
  "properties": [
    "city",
    "state",
    "status"
  ]
}

Response 200

{
  "ok": true,
  "message": "Queued 3 properties for regeneration",
  "propertiesQueued": [
    "city",
    "state",
    "status"
  ]
}
POST/api/vectors/universal/{namespace}/{type}/rebuildBearer InventDB SOAR + InventDB Serverless

Marks every sealed segment of the given type vectors_pending and runs the backfill synchronously, rebuilding all per-segment HNSW vector indexes for that type. Returns the namespace/type, segments processed, and elapsed time; 500 if the rebuild task fails. Admin/superadmin only (403 otherwise). Uses the local MiniLM embedder rather than the built-in chat LLM.

Parameters

NameInTypeDescription
namespacepathstringNamespace (database) name, e.g. 'pms'.
typepathstringType (table) name, e.g. 'properties'.

Response 200

{
  "namespace": "pms",
  "typeName": "properties",
  "processedSegments": 3,
  "elapsedMs": 1240
}

Admin: AI & Config Mixed tier

Superadmin/admin configuration: built-in LLM provider/key/model, unbounded-read row cap, Google OAuth client, and Chorus plan-cache administration.

GET/api/admin/ai-configBearer InventDB SOAR only

Returns the configured built-in LLM provider, effective and default base URL/model, whether an API key is stored (never the key itself) and where it resolves from (db/env/none), the sample-data toggle, the high-level model family, and Chorus/Sparkle settings. On managed (hosted) instances the base URL points at the InventDB AI gateway, which routes Claude to regional Amazon Bedrock inference profiles pinned to your data-residency geo (us/eu/global) and runs web search server-side via Bedrock AgentCore; the model list you can pick from is the one staff have enabled for your account. Admin or superadmin required. 403 on the InventDB Serverless tier (built-in-LLM configuration).

Response 200

{
  "provider": "anthropic",
  "base_url": "",
  "default_base_url": "https://api.anthropic.com",
  "model": "",
  "default_model": "claude-opus-4-8",
  "has_api_key": true,
  "api_key_source": "db",
  "include_sample_data": true,
  "model_family": "claude",
  "extractor_model": "",
  "chorus_enabled": true,
  "chorus_plan_first": true,
  "chorus_extractor_thinking": false,
  "sparkle_speculative": false,
  "is_sparkle": false
}
PUT/api/admin/ai-configBearer InventDB SOAR only

Saves the built-in LLM provider, API key, base URL, model, sample-data toggle, and Chorus/Sparkle settings. All fields are optional; an omitted field is left unchanged, an empty string clears (for provider/base_url/model/api_key it reverts to provider default or clears the key). On managed instances the model must be one of the models staff have enabled for your account (an unlisted model is rejected); self-hosted instances are unrestricted. Provider/key changes require a server restart to take effect for the running agent loop (restart_required:true). Admin or superadmin required. 403 on the InventDB Serverless tier (built-in-LLM configuration).

Request

{
  "provider": "anthropic",
  "api_key": "sk-ant-...",
  "base_url": "",
  "model": "",
  "include_sample_data": true,
  "extractor_model": "",
  "chorus_enabled": true,
  "chorus_plan_first": true
}

Response 200

{
  "ok": true,
  "restart_required": true,
  "error": null
}
PUT/api/admin/ai-config/model-familyBearer InventDB SOAR only

Flips the customer-level default model family, preserving the gateway client key and host. 'claude' -> Anthropic adapter (Claude Opus 4.8 / Sonnet 5 on regional Amazon Bedrock via the AI gateway); 'glm' (or deprecated alias 'open') -> OpenAI adapter (OpenRouter GLM 5.2 + Qwen3-VL); 'sparkle'/'sparkle-thinking' -> InventDB Sparkle. The target family is validated against the models staff have enabled for your account; flipping to a family with no enabled model returns 400 "… is not an enabled AI model — pick one that staff have enabled for your account". Only provider/base_url/model change; takes effect on the next AI call (no restart). Admin or superadmin required. 403 on the InventDB Serverless tier (built-in-LLM configuration).

Request

{
  "family": "glm"
}

Response 200

{
  "ok": true,
  "family": "glm",
  "provider": "openai",
  "base_url": "https://gateway.inventdb.com/v1",
  "model": "z-ai/glm-5.2",
  "error": null
}
GET/api/admin/ai-modelsBearer InventDB SOAR only

Returns the upstream model catalog for the Settings model picker. Only 'openrouter' returns a non-empty list (fetched live and cached 5 minutes, with pricing normalized to $/Mtok); 'openai'/'anthropic' return an empty array so the UI falls back to a free-text field. Admin or superadmin required. This is built-in-LLM configuration, so it 403s on the InventDB Serverless tier. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Parameters

NameInTypeDescription
providerquerystringProvider whose catalog to return (e.g. openrouter). Defaults to the configured provider.

Response 200

{
  "provider": "openrouter",
  "models": [
    {
      "id": "z-ai/glm-5.2",
      "name": "GLM 5.2",
      "description": "",
      "context_length": 200000,
      "input_price_per_mtok": 0.3,
      "output_price_per_mtok": 1.1
    }
  ],
  "source_url": "https://openrouter.ai/api/v1/models"
}
GET/api/admin/query-configBearer InventDB SOAR + InventDB Serverless

Returns the unbounded-read row cap for this instance: the stored cap, the effective cap actually enforced by /sql (env MAX_QUERY_ROWS overrides the stored value), and whether an env override is masking the setting. This is a DATABASE feature, not AI-gated, so it works on the InventDB Serverless tier. Admin or superadmin required.

Response 200

{
  "ok": true,
  "maxRows": 1000,
  "effectiveMaxRows": 1000,
  "envOverride": false
}
PUT/api/admin/query-configBearer InventDB SOAR + InventDB Serverless

Sets the unbounded-read row cap (1..10,000,000) that bounds a single unpaginated SELECT via /sql, MCP, or the API; paginated reads (?page&pageSize) are unaffected. Live on the next read (no restart). Not AI-gated, so it works on the InventDB Serverless tier (cmgmt pushes the cap to serverless instances). Admin or superadmin required.

Request

{
  "maxRows": 5000
}

Response 200

{
  "ok": true,
  "maxRows": 5000,
  "effectiveMaxRows": 5000,
  "envOverride": false
}
GET/api/admin/google-oauth-configBearer InventDB SOAR + InventDB Serverless

Returns the effective Google OAuth client_id (safe to surface), whether a client secret is stored (never returned), the source of the effective values (db/env/none), and whether the OAuth flow is fully configured. Env vars (GOOGLE_OAUTH_CLIENT_ID/_SECRET, legacy GMAIL_OAUTH_*) are a fallback until a DB value is saved. Not AI-gated (OAuth connect works without the built-in LLM), so it works on the InventDB Serverless tier. Admin or superadmin required.

Response 200

{
  "client_id": "1234567890-abc.apps.googleusercontent.com",
  "has_client_secret": true,
  "source": "db",
  "configured": true
}
PUT/api/admin/google-oauth-configBearer InventDB SOAR + InventDB Serverless

Persists a fresh Google OAuth client_id/client_secret pair into the encrypted settings record; the OAuth flow picks it up on the next call. Each field is optional: omit to leave unchanged, empty string to clear the DB value and fall back to env, non-empty to overwrite. Returns the effective config. Not AI-gated, so it works on the InventDB Serverless tier. Admin or superadmin required.

Request

{
  "client_id": "1234567890-abc.apps.googleusercontent.com",
  "client_secret": "GOCSPX-..."
}

Response 200

{
  "client_id": "1234567890-abc.apps.googleusercontent.com",
  "has_client_secret": true,
  "source": "db",
  "configured": true
}
GET/api/admin/chorus/templatesBearer InventDB SOAR + InventDB Serverless

Returns every verified Chorus (semantic plan cache) template with per-shape usage metrics (hits, misses observed on the same shape, avg hit latency, last use) plus a per-namespace rollup with hit rate for the tenant view. Metrics are read/aggregated from _system.ChorusMetrics. Inspecting the cache does not invoke the built-in LLM, so it works on the InventDB Serverless tier. Admin or superadmin required.

Response 200

{
  "templates": [
    {
      "id": "cpc_01",
      "namespace": "pms",
      "act": "read",
      "planKind": "sql",
      "toolName": "",
      "shapeKey": "properties|city|status",
      "shapeHash": "a1b2c3",
      "expectedKind": "rows",
      "verifiedBy": "replay",
      "schemaFp": "fp_88",
      "createdAt": "2026-06-20T10:00:00Z",
      "hits": 42,
      "missesOnShape": 3,
      "avgHitMs": 12,
      "lastUsed": "2026-06-23T09:15:00Z"
    }
  ],
  "namespaces": [
    {
      "namespace": "pms",
      "templates": 1,
      "hits": 42,
      "misses": 3,
      "hitRatePct": 93.3
    }
  ],
  "total": 1
}
DELETE/api/admin/chorus/templatesBearer InventDB SOAR + InventDB Serverless

Clears the whole Chorus plan cache, or every template of one tenant namespace when ?namespace is given. Deletion is always safe: the next matching message simply misses to the agent and can re-admit through the replay gate. Does not invoke the built-in LLM, so it works on the InventDB Serverless tier. Admin or superadmin required.

Parameters

NameInTypeDescription
namespacequerystringOptional tenant namespace filter; omit to clear the entire cache.

Response 200

{
  "ok": true,
  "deleted": 12,
  "namespace": "pms"
}
DELETE/api/admin/chorus/templates/{id}Bearer InventDB SOAR + InventDB Serverless

Surgically removes a single Chorus plan-cache template by id from _system.ChorusPlanCache. Returns whether the template existed. Does not invoke the built-in LLM, so it works on the InventDB Serverless tier. Admin or superadmin required.

Parameters

NameInTypeDescription
idpathstringThe _id of the ChorusPlanCache template to delete.

Response 200

{
  "ok": true,
  "deleted": true
}
POST/api/admin/chorus/dry-runBearer InventDB SOAR only

Shows how Chorus reads a question: runs extraction + canonicalization only (one small extractor LLM call; no agent, nothing served or admitted, its own usage bucket). Powers the Songbook contrast/regression view. Because it makes a built-in extractor LLM call, it returns 403 on the InventDB Serverless tier. Admin or superadmin required. 403 on the InventDB Serverless tier (this route calls the built-in LLM).

Request

{
  "message": "show vacant properties in Austin over $2000 rent",
  "namespace": "pms"
}

Response 200

{
  "outcome": "skip",
  "reason": "slot outside domain",
  "shapeKey": "q|pms|properties|list:ord=:lim=false|c=[city:eq:str,market_rent:gt:num,status:eq:str]",
  "shapeHash": "786ab0a1330a2857"
}

No endpoints match your search.

Common questions

The API, plainly.

How do I authenticate against the InventDB API?

Exchange a username and password for a signed JWT at POST /api/auth/login, then send it as Authorization: Bearer <token> on every protected request. get_current_user resolves the caller's identity and role, globally or per app.

Can I run raw SQL over HTTP?

Yes. InventDB exposes full SQL over the HTTP API alongside record CRUD, pre-built queries and bulk operations, so you can pick whichever surface fits. You are not restricted to a REST-shaped abstraction over your own data.

How many MCP tools does InventDB expose?

51, on both InventDB SOAR and InventDB Serverless. An external agent (Claude Desktop, Cursor or your own) connects over the built-in MCP server and drives the database directly. tools/list is not filtered by product, tier or role, so every client sees the same 51 tools; see MCP for the full reference. Report rendering and workflow authoring need no LLM, so they work identically on both; only a workflow step that calls an LLM fails on InventDB Serverless, and it fails when the run reaches it rather than when the tool is called.

Does the API enforce row-level security?

Yes. Every call, over REST, SQL or MCP alike, is bounded by the calling principal's role and row-level rules. An agent acting for a user can never read past what that user is allowed to see.

How do I get notified when data changes?

Through outbound webhooks, the change-signal group in this reference, which post to your endpoint as records change. Workflows can also call out directly, and can park and wait for a human for up to 14 days.

What else does the API cover?

Vector embeddings and per-property vectors, indexes, relationships, attachments with OCR, transactions, saved views, forms, reports, users and permissions, an OpenAI-compatible LLM endpoint, and Gmail, Calendar, Twilio and web-search integrations, each documented above with working examples.