{ "openapi": "3.1.0", "info": { "title": "smartbotic-vectorapi", "version": "0.1.0", "description": "General-purpose REST API fronting smartbotic-database. Supports multi-project namespacing and multi-key authorization with per-project grants." }, "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "description": "API key issued by the /api/v1/keys endpoint. Pass as Authorization: Bearer ." } }, "schemas": { "Error": { "type": "object", "properties": { "error": { "type": "string" }, "message": { "type": "string" } }, "required": ["error", "message"] }, "CollectionMeta": { "type": "object", "properties": { "name": { "type": "string" }, "kind": { "type": "string", "enum": ["json", "vector"] }, "vector_dimension": { "type": "integer", "minimum": 1 }, "embedding_model": { "type": "string" }, "created_at": { "type": "integer" } }, "required": ["name", "kind"] }, "KeyScopeRule": { "type": "object", "properties": { "collection": { "type": "string", "description": "Collection name the rule applies to" }, "ops": { "type": "array", "items": { "type": "string", "enum": ["read","list","search","insert","update","delete"] }, "description": "Permitted operations on this collection" }, "require_human_token": { "type": "boolean", "description": "Phase 2: require a CAPTCHA token (reserved)" } }, "required": ["collection", "ops"] }, "KeyScope": { "type": "object", "description": "Capability scope that constrains a non-admin key to specific collections and operations. A scoped key is safe to embed in untrusted clients (e.g. read-only, insert-only, origin-pinned, rate-limited, expiring). Scoped keys cannot manage collections or projects.", "properties": { "rules": { "type": "array", "items": { "$ref": "#/components/schemas/KeyScopeRule" }, "minItems": 1, "description": "At least one rule is required. A request is allowed only if a matching rule exists for the (collection, op) pair." }, "origins": { "type": "array", "items": { "type": "string" }, "description": "Allowed Origin header values. Empty array means any origin is permitted. When non-empty, requests without a matching Origin header are rejected (fails closed)." }, "rate_limit_per_min": { "type": "integer", "minimum": 0, "description": "Maximum requests per minute for this key (keyed by key id). 0 means unlimited. Exceeding the limit returns 429 with a Retry-After: 60 header." }, "expires_at": { "type": "integer", "description": "Unix epoch seconds after which the key is treated as expired (auth returns 401). 0 means the key never expires." } }, "required": ["rules"] }, "ApiKeyPublic": { "type": "object", "properties": { "id": { "type": "string", "description": "Stable non-secret identifier used to reference the key in PATCH/DELETE" }, "key_prefix": { "type": "string" }, "label": { "type": "string" }, "projects": { "type": "array", "items": { "type": "string" } }, "admin": { "type": "boolean" }, "created_at": { "type": "integer" }, "scope": { "$ref": "#/components/schemas/KeyScope", "description": "Present only when the key carries a capability scope" } } } } }, "security": [{ "bearerAuth": [] }], "paths": { "/healthz": { "get": { "summary": "Liveness check — always 200 if the process is running", "operationId": "healthz", "security": [], "responses": { "200": { "description": "OK" } } } }, "/readyz": { "get": { "summary": "Readiness check — 200 when the database is reachable", "operationId": "readyz", "security": [], "responses": { "200": { "description": "Ready" }, "503": { "description": "Database not reachable" } } } }, "/api/v1/projects": { "get": { "summary": "List projects the key may access (admin sees all)", "operationId": "listProjects", "responses": { "200": { "description": "Project list", "content": { "application/json": { "schema": { "type": "object", "properties": { "projects": { "type": "array", "items": { "type": "string" } } } } } } }, "401": { "description": "Unauthorized" } } }, "post": { "summary": "Create a new project (admin only)", "operationId": "createProject", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] } } } }, "responses": { "201": { "description": "Project created" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" }, "422": { "description": "Validation error" } } } }, "/api/v1/projects/{project}": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" } ], "get": { "summary": "Get project info (collection and document counts)", "operationId": "getProject", "responses": { "200": { "description": "Project info", "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "collections": { "type": "integer" }, "documents": { "type": "integer" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Project not found" } } }, "delete": { "summary": "Drop a project (admin only; not 'default')", "operationId": "deleteProject", "responses": { "200": { "description": "Project deleted" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required or cannot drop default" }, "404": { "description": "Project not found" } } } }, "/api/v1/keys": { "get": { "summary": "List API keys (admin only; secret masked; returns id and key_prefix)", "operationId": "listKeys", "responses": { "200": { "description": "Key list", "content": { "application/json": { "schema": { "type": "object", "properties": { "keys": { "type": "array", "items": { "$ref": "#/components/schemas/ApiKeyPublic" } } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" } } }, "post": { "summary": "Generate a new API key (admin only); returns the secret key value once", "operationId": "createKey", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "label": { "type": "string" }, "projects": { "type": "array", "items": { "type": "string" }, "description": "Project names the key may access; use [\"*\"] for all projects" }, "admin": { "type": "boolean", "default": false }, "scope": { "$ref": "#/components/schemas/KeyScope", "description": "Optional capability scope. Mutually exclusive with admin:true." } }, "required": ["label", "projects"] } } } }, "responses": { "201": { "description": "Key created; contains the secret key value (shown once) and the stable id", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "Stable non-secret identifier for this key" }, "key": { "type": "string", "description": "Secret key value — shown only once" }, "label": { "type": "string" }, "projects": { "type": "array", "items": { "type": "string" } }, "admin": { "type": "boolean" }, "scope": { "$ref": "#/components/schemas/KeyScope" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" }, "422": { "description": "Validation error" } } } }, "/api/v1/keys/{id}": { "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Stable non-secret key id (from the id field in list/create responses)" } ], "patch": { "summary": "Update key grants (admin only)", "operationId": "updateKey", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "label": { "type": "string" }, "projects": { "type": "array", "items": { "type": "string" } }, "admin": { "type": "boolean" }, "scope": { "$ref": "#/components/schemas/KeyScope", "description": "Set or replace the capability scope. Omit to leave unchanged. Pass null to clear the scope." } } } } } }, "responses": { "200": { "description": "Key updated" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" }, "404": { "description": "Key not found" } } }, "delete": { "summary": "Revoke an API key (admin only)", "operationId": "deleteKey", "responses": { "200": { "description": "Key revoked" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" }, "404": { "description": "Key not found" } } } }, "/api/v1/projects/{project}/collections": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" } ], "get": { "summary": "List collections in a project", "operationId": "listCollections", "parameters": [ { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Case-insensitive substring filter on collection name (applied server-side)." }, { "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["name", "kind", "documents", "size", "created_at"], "default": "name" }, "description": "Field to sort by (applied server-side). Ties break by name." }, { "name": "desc", "in": "query", "schema": { "type": "boolean", "default": false } } ], "responses": { "200": { "description": "Collection list", "content": { "application/json": { "schema": { "type": "object", "properties": { "collections": { "type": "array", "items": { "$ref": "#/components/schemas/CollectionMeta" } } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" } } }, "post": { "summary": "Create a collection in a project", "operationId": "createCollection", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "kind": { "type": "string", "enum": ["json", "vector"] }, "vector_dimension": { "type": "integer", "minimum": 1, "description": "Required when kind=vector" }, "embedding_model": { "type": "string", "description": "Optional; defaults to global default_embedding_model" } }, "required": ["name", "kind"] } } } }, "responses": { "201": { "description": "Collection created" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "422": { "description": "Validation error (e.g. missing vector_dimension for vector kind)" } } } }, "/api/v1/projects/{project}/collections/{name}": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Collection name" } ], "get": { "summary": "Get collection metadata", "operationId": "getCollection", "responses": { "200": { "description": "Collection metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CollectionMeta" } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" } } }, "delete": { "summary": "Drop a collection and all its documents", "operationId": "deleteCollection", "responses": { "200": { "description": "Collection deleted" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" } } } }, "/api/v1/projects/{project}/collections/{name}/documents": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Collection name" } ], "get": { "summary": "Find documents with optional filter, pagination, and sort", "operationId": "findDocuments", "parameters": [ { "name": "filter", "in": "query", "schema": { "type": "string" }, "description": "Filter expression in field:op:value grammar (e.g. age:gte:30, name:eq:Alice). Multiple filters are ANDed." }, { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } }, { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }, { "name": "sort", "in": "query", "schema": { "type": "string" }, "description": "Field to sort by" }, { "name": "desc", "in": "query", "schema": { "type": "boolean", "default": false } } ], "responses": { "200": { "description": "Document list", "content": { "application/json": { "schema": { "type": "object", "properties": { "documents": { "type": "array", "items": { "type": "object" } }, "count": { "type": "integer" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } }, "post": { "summary": "Insert a new JSON document", "operationId": "insertDocument", "parameters": [ { "name": "X-Captcha-Token", "in": "header", "required": false, "schema": { "type": "string" }, "description": "CAPTCHA response token. Required when the scoped key rule has require_human_token=true; verified server-side via the configured captcha provider." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "description": "Arbitrary JSON document payload" } } } } } }, "responses": { "201": { "description": "Document inserted", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" }, "422": { "description": "Validation error" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } } }, "/api/v1/projects/{project}/collections/{name}/documents/{id}": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Collection name" }, { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Document ID" } ], "get": { "summary": "Get a document by ID", "operationId": "getDocument", "responses": { "200": { "description": "Document", "content": { "application/json": { "schema": { "type": "object" } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Document not found" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } }, "put": { "summary": "Replace a document (full upsert by ID)", "operationId": "replaceDocument", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Document replaced" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" }, "422": { "description": "Validation error" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } }, "patch": { "summary": "Partially update a document (merge fields)", "operationId": "patchDocument", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Document updated" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Document not found" }, "422": { "description": "Validation error" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } }, "delete": { "summary": "Delete a document by ID", "operationId": "deleteDocument", "responses": { "200": { "description": "Document deleted" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Document not found" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } } }, "/api/v1/projects/{project}/collections/{name}/vectors": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Collection name (must be kind=vector)" } ], "post": { "summary": "Store a vector; supply either text (auto-embedded) or an explicit vector array", "description": "Store a vector. Supply either `text` (auto-embedded) or a pre-computed `vector` array. When supplying `text`, the server-side embedding result is cached by default. Send `Cache-Control: no-store` to bypass the cache: the text will be re-embedded fresh and the result will not be stored.", "operationId": "upsertVector", "parameters": [ { "name": "X-Captcha-Token", "in": "header", "required": false, "schema": { "type": "string" }, "description": "CAPTCHA response token. Required when the scoped key rule has require_human_token=true; verified server-side via the configured captcha provider." } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string", "description": "Optional stable ID; auto-generated if omitted" }, "text": { "type": "string", "description": "Source text; will be embedded using the collection's embedding_model" }, "vector": { "type": "array", "items": { "type": "number" }, "description": "Pre-computed embedding; dimension must match the collection's vector_dimension" }, "metadata": { "type": "object", "description": "Arbitrary JSON payload stored alongside the vector" } } } } } }, "responses": { "201": { "description": "Vector stored", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" }, "422": { "description": "Validation error (wrong dimension, not a vector collection, etc.)" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } } }, "/api/v1/projects/{project}/collections/{name}/search": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" }, { "name": "name", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Collection name (must be kind=vector)" } ], "post": { "summary": "Cosine-similarity search; supply either query_text (auto-embedded) or query_vector", "description": "Supply either `query_text` or `query_vector`. **For low latency, prefer `query_vector`.** `query_text` requires a synchronous server-side embedding round-trip to the configured provider, which typically dominates request latency (seconds for large models). Clients issuing many searches (e.g. an LLM/n8n agent) should embed once on their side and pass `query_vector` to skip that step entirely; cosine search itself is sub-millisecond. Send `Cache-Control: no-store` to bypass the server-side embedding cache: the query text will be re-embedded fresh and the result will not be stored.", "operationId": "searchVectors", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "query_text": { "type": "string", "description": "Query text; embedded with the collection's model. Slow path: incurs a server-side embedding round-trip. Prefer query_vector when you can pre-embed." }, "query_vector": { "type": "array", "items": { "type": "number" }, "description": "Pre-computed query embedding (length must equal the collection's vector_dimension). Fast path: skips server-side embedding." }, "top_k": { "type": "integer", "minimum": 1, "description": "Maximum number of results to return" }, "min_score": { "type": "number", "description": "Minimum cosine similarity score (0–1)" }, "filters": { "type": "array", "items": { "type": "string" }, "description": "Metadata filters in field:op:value grammar (e.g. src:eq:n8n)" } }, "required": ["top_k"] } } } }, "responses": { "200": { "description": "Search results", "content": { "application/json": { "schema": { "type": "object", "properties": { "results": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "score": { "type": "number" }, "metadata": { "type": "object" } } } } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Collection not found" }, "422": { "description": "Validation error" }, "429": { "description": "Too Many Requests (rate-limited scoped key)" } } } }, "/api/v1/projects/{project}/stats": { "parameters": [ { "name": "project", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project name" } ], "get": { "summary": "Per-project stats (accessible by any key granted the project)", "operationId": "projectStats", "responses": { "200": { "description": "Project stats", "content": { "application/json": { "schema": { "type": "object", "properties": { "project": { "type": "string" }, "collections": { "type": "integer" }, "documents": { "type": "integer" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" } } } }, "/api/v1/settings": { "get": { "summary": "Get current settings (admin only); openai_api_key is masked to a boolean openai_api_key_set", "operationId": "getSettings", "responses": { "200": { "description": "Settings (with secret masked)", "content": { "application/json": { "schema": { "type": "object", "properties": { "openai_api_base": { "type": "string" }, "openai_api_key_set": { "type": "boolean" }, "default_embedding_model": { "type": "string" }, "cors_origins": { "type": "array", "items": { "type": "string" } }, "session_ttl_minutes": { "type": "integer" }, "webui_enabled": { "type": "boolean" }, "default_project": { "type": "string" }, "embedding_connect_timeout_sec": { "type": "integer" }, "embedding_read_timeout_sec": { "type": "integer" }, "embedding_cache_size": { "type": "integer" }, "embedding_cache_ttl_sec": { "type": "integer" }, "embedding_cache_max_bytes": { "type": "integer" }, "embedding_cache_normalize": { "type": "boolean" }, "embedding_client_pool_size": { "type": "integer" }, "captcha_provider": { "type": "string" }, "captcha_secret_set": { "type": "boolean", "description": "True when a captcha_secret is configured (the secret itself is never returned)" }, "captcha_verify_url": { "type": "string" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" } } }, "put": { "summary": "Update settings (admin only); omit openai_api_key to keep the existing secret", "operationId": "updateSettings", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "openai_api_base": { "type": "string" }, "openai_api_key": { "type": "string", "description": "Omit or pass empty string to preserve the existing key" }, "default_embedding_model": { "type": "string" }, "cors_origins": { "type": "array", "items": { "type": "string" } }, "session_ttl_minutes": { "type": "integer", "minimum": 1 }, "webui_enabled": { "type": "boolean" }, "default_project": { "type": "string" }, "embedding_connect_timeout_sec": { "type": "integer", "minimum": 1, "description": "Embedding HTTP connect timeout in seconds (default 10)" }, "embedding_read_timeout_sec": { "type": "integer", "minimum": 1, "description": "Embedding HTTP read timeout in seconds (default 60)" }, "embedding_cache_size": { "type": "integer", "minimum": 0, "description": "Maximum number of cached embedding entries (default 4096)" }, "embedding_cache_ttl_sec": { "type": "integer", "minimum": 0, "description": "Cache entry TTL in seconds; 0 = no expiry (default 86400)" }, "embedding_cache_max_bytes": { "type": "integer", "minimum": 0, "description": "Maximum total bytes of cached vectors (default 268435456 = 256 MB)" }, "embedding_cache_normalize": { "type": "boolean", "description": "When true, text is trimmed and lower-cased before cache lookup (default false)" }, "embedding_client_pool_size": { "type": "integer", "minimum": 1, "description": "Number of keep-alive HTTP clients pooled per upstream origin (default 4)" }, "captcha_provider": { "type": "string", "description": "CAPTCHA provider: empty string to disable, 'turnstile', or 'hcaptcha'" }, "captcha_secret": { "type": "string", "description": "CAPTCHA server-side secret. Omit or pass empty string to preserve the existing secret." }, "captcha_verify_url": { "type": "string", "description": "Full URL of the CAPTCHA verify endpoint (e.g. https://challenges.cloudflare.com/turnstile/v0/siteverify)" } } } } } }, "responses": { "200": { "description": "Settings updated", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" } } } }, "/api/v1/stats": { "get": { "summary": "Server-wide stats (admin only)", "operationId": "globalStats", "responses": { "200": { "description": "Global stats", "content": { "application/json": { "schema": { "type": "object", "properties": { "projects": { "type": "integer" }, "memory_pressure_level": { "type": "integer" }, "collections": { "type": "integer" }, "documents": { "type": "integer" }, "embedding": { "type": "object", "properties": { "cache_size": { "type": "integer" }, "cache_capacity": { "type": "integer" }, "cache_hits": { "type": "integer" }, "cache_misses": { "type": "integer" }, "cache_hit_ratio": { "type": "number" }, "cache_bytes": { "type": "integer" } } } } } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden — admin required" } } } } } }