|
@@ -0,0 +1,175 @@
|
|
|
|
|
+# smartbotic-vectorapi — API Reference
|
|
|
|
|
+
|
|
|
|
|
+Version 0.1.0. This is the human-readable reference. Machine-readable equivalents
|
|
|
|
|
+are served live by the running service and shipped in the package:
|
|
|
|
|
+
|
|
|
|
|
+- **`GET /openapi.json`** — OpenAPI 3.1 spec (source: `api/openapi.json`).
|
|
|
|
|
+- **`GET /llms.txt`** — a compact, agent-friendly summary following the
|
|
|
|
|
+ [llmstxt.org](https://llmstxt.org) convention (source: `api/llms.txt`).
|
|
|
|
|
+- **`GET /docs`** — this reference rendered as an interactive page (Redoc).
|
|
|
|
|
+
|
|
|
|
|
+## Base & authentication
|
|
|
|
|
+
|
|
|
|
|
+- Base path: **`/api/v1`**. All `/api/v1/*` routes require authentication.
|
|
|
|
|
+- **API clients** send an API key as a bearer token: `Authorization: Bearer <KEY>`.
|
|
|
|
|
+- **The web UI** logs in with a key (`POST /ui/login`) and rides a cookie session
|
|
|
|
|
+ thereafter (`credentials: include`).
|
|
|
|
|
+- Public (no auth): `GET /healthz`, `GET /readyz`, `GET /openapi.json`,
|
|
|
|
|
+ `GET /llms.txt`, `GET /docs`, and the static UI.
|
|
|
|
|
+
|
|
|
|
|
+### Authorization model (MySQL-like grants)
|
|
|
|
|
+
|
|
|
|
|
+Each API key carries grants: a set of project names it may access (or `"*"` for
|
|
|
|
|
+all) plus an `admin` flag.
|
|
|
|
|
+
|
|
|
|
|
+| Route class | Requirement | On failure |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `/api/v1/projects/{project}/…` (collections, documents, vectors, search, project stats) | key is `admin`, or holds `"*"`, or lists `{project}` | **403** |
|
|
|
|
|
+| `POST/DELETE /api/v1/projects`, all `/api/v1/keys`, `GET /api/v1/stats` | `admin` | **403** |
|
|
|
|
|
+| `GET /api/v1/projects` | any valid key (result filtered to granted projects) | — |
|
|
|
|
|
+| any `/api/v1/*` with a missing/unknown key | — | **401** |
|
|
|
|
|
+
|
|
|
|
|
+On first run with no keys, the service generates one **admin** key
|
|
|
|
|
+(`projects:["*"], admin:true`) and logs it once.
|
|
|
|
|
+
|
|
|
|
|
+### Errors
|
|
|
|
|
+
|
|
|
|
|
+All errors return JSON `{"error": {"code": "<slug>", "message": "<text>"}}` with a
|
|
|
|
|
+standard HTTP status: `400` malformed request, `401` unauthenticated, `403`
|
|
|
|
|
+forbidden (valid key lacking the grant), `404` not found, `422` validation
|
|
|
|
|
+(bad body, dimension mismatch, reserved name), `503` database/embedding
|
|
|
|
|
+unavailable, `500` unexpected.
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Projects
|
|
|
|
|
+
|
|
|
|
|
+A project is an isolated namespace (collections in one project are invisible to
|
|
|
|
|
+others). `default` always exists and cannot be deleted.
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Auth | Body / notes |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `GET` | `/api/v1/projects` | any key | → `{"projects": ["default", ...]}` (only granted projects) |
|
|
|
|
|
+| `POST` | `/api/v1/projects` | admin | `{"name":"acme"}` — name `^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$` → `201 {"name":"acme"}` |
|
|
|
|
|
+| `GET` | `/api/v1/projects/{project}` | project access | → `{"name","collections"}` |
|
|
|
|
|
+| `DELETE` | `/api/v1/projects/{project}` | admin | drops the project + its collections (not `default`) |
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+curl -s -X POST $BASE/api/v1/projects -H "Authorization: Bearer $KEY" \
|
|
|
|
|
+ -H 'Content-Type: application/json' -d '{"name":"acme"}'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## API keys (admin only)
|
|
|
|
|
+
|
|
|
|
|
+Keys are referenced by a stable, non-secret **`id`** (from the list); the secret
|
|
|
|
|
+value is only shown once, at creation.
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Body / notes |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `GET` | `/api/v1/keys` | → `{"keys":[{id,label,projects,admin,created_at,key_prefix}]}` (secret masked) |
|
|
|
|
|
+| `POST` | `/api/v1/keys` | `{"label":"n8n","projects":["acme"],"admin":false}` → `201` with the full `key` **once** |
|
|
|
|
|
+| `PATCH` | `/api/v1/keys/{id}` | `{label?,projects?,admin?}` — update grants |
|
|
|
|
|
+| `DELETE` | `/api/v1/keys/{id}` | revoke (rejected with `422` if it's the last admin) |
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# create a scoped key for n8n, limited to project "acme"
|
|
|
|
|
+curl -s -X POST $BASE/api/v1/keys -H "Authorization: Bearer $ADMIN" \
|
|
|
|
|
+ -H 'Content-Type: application/json' \
|
|
|
|
|
+ -d '{"label":"n8n-acme","projects":["acme"],"admin":false}'
|
|
|
|
|
+# → {"id":"...","label":"n8n-acme","projects":["acme"],"admin":false,"key":"<shown once>"}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Collections
|
|
|
|
|
+
|
|
|
|
|
+Collections are **admin-managed**: create one before writing. `kind` is `json`
|
|
|
|
|
+(documents) or `vector` (RAG; fixed `vector_dimension`). Names may not start with
|
|
|
|
|
+`_` or the reserved `vectorapi_` prefix.
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Body / notes |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `POST` | `/api/v1/projects/{p}/collections` | `{"name","kind":"json"\|"vector","vector_dimension"?,"embedding_model"?}` |
|
|
|
|
|
+| `GET` | `/api/v1/projects/{p}/collections` | → `{"collections":[{name,kind,vector_dimension,embedding_model,document_count,size_bytes}]}` |
|
|
|
|
|
+| `GET` | `/api/v1/projects/{p}/collections/{name}` | one collection's info |
|
|
|
|
|
+| `DELETE` | `/api/v1/projects/{p}/collections/{name}` | drop it |
|
|
|
|
|
+
|
|
|
|
|
+`embedding_model` (vector collections) is the OpenAI model used when storing/
|
|
|
|
|
+searching by text; its output dimension must equal `vector_dimension`
|
|
|
|
|
+(`text-embedding-3-small` = 1536, `text-embedding-3-large` = 3072).
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+curl -s -X POST $BASE/api/v1/projects/acme/collections -H "Authorization: Bearer $KEY" \
|
|
|
|
|
+ -H 'Content-Type: application/json' \
|
|
|
|
|
+ -d '{"name":"memories","kind":"vector","vector_dimension":1536,"embedding_model":"text-embedding-3-small"}'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## JSON documents
|
|
|
|
|
+
|
|
|
|
|
+Under `/api/v1/projects/{p}/collections/{name}/documents`.
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Body / notes |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `POST` | `.../documents` | `{"id"?,"data":{...}}` (or the raw doc) → `201 {"id"}` |
|
|
|
|
|
+| `GET` | `.../documents/{id}` | the document |
|
|
|
|
|
+| `GET` | `.../documents` | find — query params below → `{"documents":[...],"count"}` |
|
|
|
|
|
+| `PATCH` | `.../documents/{id}` | merge fields → `{"id","version"}` |
|
|
|
|
|
+| `PUT` | `.../documents/{id}` | replace the whole document |
|
|
|
|
|
+| `DELETE` | `.../documents/{id}` | delete |
|
|
|
|
|
+
|
|
|
|
|
+**Find query params:** `filter` (repeatable, `field:op:value`, AND-combined),
|
|
|
|
|
+`limit`, `offset`, `sort`, `desc` (`true`/`false`). Operators (`op`): `eq`, `ne`,
|
|
|
|
|
+`gt`, `gte`, `lt`, `lte`, `in`, `contains`, `exists`, `regex`, `search`. The value
|
|
|
|
|
+is parsed as JSON when possible, else as a string.
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+curl -s -X POST $BASE/api/v1/projects/acme/collections/users/documents -H "Authorization: Bearer $KEY" \
|
|
|
|
|
+ -H 'Content-Type: application/json' -d '{"data":{"name":"Alice","age":30}}'
|
|
|
|
|
+
|
|
|
|
|
+curl -s "$BASE/api/v1/projects/acme/collections/users/documents?filter=age:gte:18&filter=name:search:Ali&limit=20&sort=age&desc=true" \
|
|
|
|
|
+ -H "Authorization: Bearer $KEY"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## RAG: vectors & similarity search
|
|
|
|
|
+
|
|
|
|
|
+Under `/api/v1/projects/{p}/collections/{name}` (vector collections only).
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Body |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `POST` | `.../vectors` | `{"id"?,"text"?,"vector"?,"metadata"?}` — provide `vector` to store as-is, or `text` to embed (OpenAI) → `201 {"id"}` |
|
|
|
|
|
+| `POST` | `.../search` | `{"query_text"?\|"query_vector"?,"top_k":5,"min_score":0.0}` → `{"results":[{id,score,data}]}` ordered by descending cosine similarity |
|
|
|
|
|
+
|
|
|
|
|
+A provided/generated vector's length must equal the collection's
|
|
|
|
|
+`vector_dimension` (else `422`). Storing by `text` or searching by `query_text`
|
|
|
|
|
+requires `OPENAI_API_KEY` to be configured (else `422`/`503`).
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+curl -s -X POST $BASE/api/v1/projects/acme/collections/memories/vectors -H "Authorization: Bearer $KEY" \
|
|
|
|
|
+ -H 'Content-Type: application/json' -d '{"text":"user prefers dark mode","metadata":{"src":"n8n"}}'
|
|
|
|
|
+
|
|
|
|
|
+curl -s -X POST $BASE/api/v1/projects/acme/collections/memories/search -H "Authorization: Bearer $KEY" \
|
|
|
|
|
+ -H 'Content-Type: application/json' -d '{"query_text":"dark mode","top_k":5,"min_score":0.7}'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Stats & ops
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Auth | Returns |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `GET` | `/api/v1/projects/{p}/stats` | project access | `{project, collections, documents}` |
|
|
|
|
|
+| `GET` | `/api/v1/stats` | admin | server-wide: documents, collections, memory, pressure level, project count |
|
|
|
|
|
+| `GET` | `/healthz` | public | `{"status":"ok"}` (liveness) |
|
|
|
|
|
+| `GET` | `/readyz` | public | `{"ready":bool}` — `200` if the database is reachable, else `503` |
|
|
|
|
|
+
|
|
|
|
|
+## Web UI session
|
|
|
|
|
+
|
|
|
|
|
+| Method | Path | Body / notes |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `POST` | `/ui/login` | `{"key":"<API key>"}` → sets an HttpOnly `svapi_session` cookie; body `{ok,admin,projects}` |
|
|
|
|
|
+| `POST` | `/ui/logout` | clears the session cookie |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## n8n usage notes
|
|
|
|
|
+
|
|
|
|
|
+- Use an **HTTP Request** node with **Header Auth** / a generic `Authorization: Bearer <key>` header.
|
|
|
|
|
+- Give each workflow (or tenant) its own **project** and a **scoped key** granted only that project — so a leaked key can't reach other tenants' data.
|
|
|
|
|
+- For RAG: create a `vector` collection with the embedding model matching your dimension, `POST .../vectors` with `text` to ingest, and `POST .../search` with `query_text` to retrieve context for the LLM node.
|
|
|
|
|
+- For everything else: a `json` collection with `POST .../documents` (store) and `GET .../documents?filter=...` (query).
|