Quick start
Sign up, log in, make a key, then create a namespace, write documents and search. You can also do the first three steps on the sign-up page.
B=https://skimmerdb.com
curl -s $B/api/signup -d '{"email": "ada@example.com", "name": "Ada", "password": "correct horse"}'
JWT=$(curl -s $B/api/login -d '{"email": "ada@example.com", "password": "correct horse"}' | jq -r .token)
KEY=$(curl -s $B/api/keys -H "Authorization: Bearer $JWT" -d '{"name": "my agent"}' | jq -r .key)
curl -s $B/api/namespaces -H "Authorization: Bearer $KEY" -d '{"name": "support"}'
curl -s $B/api/namespaces/support/docs -H "Authorization: Bearer $KEY" -d '{
"wait": true,
"docs": [
{"id": "t-1", "text": "Refund requested for order ACME-1042", "meta": {"status": "open", "priority": 3}},
{"id": "t-2", "text": "Login fails with error E401 on mobile", "meta": {"status": "closed", "priority": 1}}
]}'
curl -s $B/api/namespaces/support/search -H "Authorization: Bearer $KEY" \
-d '{"q": "acme-1042", "filter": {"status": "open"}}'
# {"namespace":"support","hits":[{"id":"t-1","match":"ACME-1042","line":"Refund requested for order ACME-1042",
# "meta":{"priority":3,"status":"open"},"written_at":"2026-10-05T01:30:12Z"}],"next":"","docs":2,
# "took_ms":0.41,"partial":false,"temperature":"warm","store_gets":0,"store_bytes":0}Concepts
- Tenant
- An account. It owns namespaces, keys, usage and a bill. A person can belong to several; a login acts for the default one, or the one named by
X-Tenant: <id>. - Namespace
- A named set of documents, as many as you like. It lives in object storage: nothing runs for it while nobody uses it.
- Document
{"id", "text", "meta", "vector"}. Writing an id again replaces the document.metais a JSON object you can filter on.- Durable on write
- A write is answered once it is stored in object storage (writes are grouped every 250 ms). It is searchable on that server at once and everywhere within about a second; send
"wait": trueto answer only once it is searchable. - Temperature
- Every search, count and read reports
temperature(warm: open;local: from the server's disk;cold: from object storage) and what it read from object storage (store_gets,store_bytes).
Accounts and keys
Keys belong to one tenant. Making and listing keys needs the owner's login or an admin key.
POST /api/signup {email, name, password} | A person and their first tenant. |
POST /api/login {email, password} | {token}: a login token for a day, to make keys with. |
GET /api/me | You, the tenant you act for, its plan and your scope. |
POST /api/keys {name, scope, namespaces, rate_per_s, cap_queries, cap_writes} | A key, shown only in this answer. scope: read, write (default) or admin. namespaces: names or prefix* (default all). Caps: 0 means none. |
GET /api/keys | The tenant’s live keys (connected_app: made by an OAuth approval). |
PUT /api/keys/{id}/limits | Change a key’s namespaces and caps, at once. |
DELETE /api/keys/{id} | Revoke a key at once (an MCP connection too). |
Namespaces
POST /api/namespaces {name, vector: {dims, metric}} | Create. vector is optional: dims 1 to 2,048, metric cosine (default) or euclidean. |
GET /api/namespaces?after=&limit=100 | List by name, with documents and bytes written and stored. Pass next as after. |
GET /api/namespaces/{name} | One namespace. |
DELETE /api/namespaces/{name} | Delete it; its documents are removed from storage right after. |
Documents
# put (insert or replace) and delete in one request: 1 to 10,000 documents, 64 MB at most curl -s $B/api/namespaces/support/docs -H "Authorization: Bearer $KEY" \ -d '{"docs": [{"id": "t-3", "text": "Webhook timeouts since Tuesday", "meta": {"team": "api"}}], "deletes": ["t-2"]}' # {"written": 1, "deleted": 1, "version": 3, "stored_bytes": 2817} # update: text replaced if given, meta merged key by key (null removes a key) curl -s -X PATCH $B/api/namespaces/support/docs -H "Authorization: Bearer $KEY" \ -d '{"docs": [{"id": "t-1", "meta": {"status": "closed", "priority": null}}]}' # delete by id curl -s $B/api/namespaces/support/delete -H "Authorization: Bearer $KEY" -d '{"ids": ["t-3"]}' # read one whole (ids may contain slashes) curl -s $B/api/namespaces/support/docs/t-1 -H "Authorization: Bearer $KEY"
Search
POST /api/namespaces/{name}/search with {"q", "regex", "filter", "limit", "cursor"}, or GET with the same query parameters (filter as JSON).
curl -s $B/api/namespaces/support/search -H "Authorization: Bearer $KEY" \
-d '{"q": "E[0-9]{3}", "regex": true, "filter": {"priority": {"gte": 1, "lt": 4}}, "limit": 50}'q: plain text, matched literally and case-insensitively. With"regex": true, an RE2 pattern (case-sensitive unless it starts with(?i)). Empty: list documents.filter: every key must hold.{"status": "open"}equal;{"status": ["open", "pending"]}or{"in": [...]}one of;{"gte": 2, "lt": 5}a range (gt gte lt lte);{"ne": "api"}not equal.- Hits come newest first, one per document, with the matched text and its line. Pass
nextback ascursorfor the next page; cursors survive compaction. partial: truemeans object storage failed for some reads, so hits may be missing.
Vector search
Create the namespace with vector, write documents with "vector": [...] (or vector_b64, little-endian float32), and search with a vector. q and filter narrow the candidates first: the answer is the nearest among those that pass.
# a namespace with vectors: dims and metric are fixed for its life curl -s $B/api/namespaces -H "Authorization: Bearer $KEY" \ -d '{"name": "notes", "vector": {"dims": 768, "metric": "cosine"}}' curl -s $B/api/namespaces/notes/search -H "Authorization: Bearer $KEY" \ -d '{"vector": [0.12, -0.03, ...], "limit": 10, "filter": {"team": "api"}, "q": "timeout"}' # {"namespace":"notes","hits":[{"id":"n-17","score":0.912,"meta":{"team":"api"},"written_at":"..."}], # "docs":1344643,"took_ms":0.73,"scanned":15232,"reranked":64,"partial":false,"temperature":"warm"}
score: cosine similarity, or euclidean distance.limit10 (up to 1,000). Accuracy:nprobe(48),rerank(64),full: trueto rerank with the float32 vectors. Defaults give recall@10 of about 0.97 on 1.3M 768-d vectors in under a millisecond warm.- Recent writes are searched exactly; compaction builds an IVF index over 1-bit codes with int8 reranking. A cold query takes two parallel rounds to object storage.
Count
POST /api/namespaces/{name}/count {"q", "regex", "filter"}: {"count", "exact", "sample", ...}. Exact by default; "exact": false estimates fast on big namespaces.
Warm
POST /api/namespaces/{name}/warm {"queries": ["refund", "ERR-404"]} opens a namespace ahead of time and fetches what those searches need, so an agent about to work on it gets warm answers from its first query.
Usage
GET /api/usage: this billing period so far and what it costs (?days=1 adds a day-by-day breakdown): queries, documents and bytes written, storage, the MCP share, and object-storage reads.
MCP
https://skimmerdb.com/mcp speaks MCP over Streamable HTTP. Agents sign in with OAuth 2.1 (dynamic client registration, PKCE): the user picks the tenant, read or read-write, and optionally the namespaces. API keys work too. Every tool call is scoped, limited and metered like REST.
# Claude Code (OAuth in the browser, or add --header "Authorization: Bearer tb_...") claude mcp add --transport http skimmerdb https://skimmerdb.com/mcp # Cursor: ~/.cursor/mcp.json {"mcpServers": {"skimmerdb": {"url": "https://skimmerdb.com/mcp"}}} # Codex: ~/.codex/config.toml (then: codex mcp login skimmerdb) [mcp_servers.skimmerdb] url = "https://skimmerdb.com/mcp" # Claude and ChatGPT: add a custom connector / remote MCP server with the URL above
| Tool | Arguments |
|---|---|
list_namespaces | after, limit |
create_namespace | name, dims, metric |
delete_namespace | name |
write_documents | namespace, docs [{id, text, meta, vector}], deletes, wait |
delete_documents | namespace, ids, wait |
search | namespace, query, regex, filter, limit, cursor, vector |
count | namespace, query, regex, filter, exact |
get_document | namespace, id |
warm_namespace | namespace, queries, regex |
Limits
| Namespaces per tenant | Unlimited |
| Namespace name | 1 to 64 of a-z 0-9 - _ . |
| Document id | Up to 512 bytes, one line |
| Document text | 2 MB |
| Metadata | 16 KB JSON object: strings, numbers, booleans |
| Documents per write | 10,000 (64 MB) |
| Vector dimensions | 1 to 2,048 |
| Results per query | 1,000 |
| Requests per second (free / paid), per server | 100 / 1,000 |
| Requests in flight (free / paid), per server | 16 / 64 |
| Storage (free / paid) | 1 GB / 10 TB |
Errors
Errors are {"error": "..."} with the HTTP status.
400 | Malformed request |
401 | No key, or a bad one |
402 | The free plan’s month is used up |
403 | The key’s scope doesn’t allow it, or the account is suspended |
404 | No such namespace or document |
409 | Already exists |
413 | Too large |
422 | Invalid; the message says why |
429 | Over a rate, concurrency or cap: retry after Retry-After |
503 | Object storage failed: retry; nothing was written |
507 | The storage quota is full |