docs

a REST API and an MCP server over the same engine. JSON in, JSON out. Send your key as Authorization: Bearer tb_...

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.

shell
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. meta is 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": true to 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/meYou, 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/keysThe tenant’s live keys (connected_app: made by an OAuth approval).
PUT /api/keys/{id}/limitsChange 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=100List 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

shell
# 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"

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.

shell
# 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.
  • limit 10 (up to 1,000). Accuracy: nprobe (48), rerank (64), full: true to 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.

connect
# 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
ToolArguments
list_namespacesafter, limit
create_namespacename, dims, metric
delete_namespacename
write_documentsnamespace, docs [{id, text, meta, vector}], deletes, wait
delete_documentsnamespace, ids, wait
searchnamespace, query, regex, filter, limit, cursor, vector
countnamespace, query, regex, filter, exact
get_documentnamespace, id
warm_namespacenamespace, queries, regex

Limits

Namespaces per tenantUnlimited
Namespace name1 to 64 of a-z 0-9 - _ .
Document idUp to 512 bytes, one line
Document text2 MB
Metadata16 KB JSON object: strings, numbers, booleans
Documents per write10,000 (64 MB)
Vector dimensions1 to 2,048
Results per query1,000
Requests per second (free / paid), per server100 / 1,000
Requests in flight (free / paid), per server16 / 64
Storage (free / paid)1 GB / 10 TB

Errors

Errors are {"error": "..."} with the HTTP status.

400Malformed request
401No key, or a bad one
402The free plan’s month is used up
403The key’s scope doesn’t allow it, or the account is suspended
404No such namespace or document
409Already exists
413Too large
422Invalid; the message says why
429Over a rate, concurrency or cap: retry after Retry-After
503Object storage failed: retry; nothing was written
507The storage quota is full