Structured Similarity & Anomaly Engine
Load your catalog — products, transactions, sensor readings — as plain records. Get similar records and statistical outliers back, directly on your raw numeric fields. No vectors. No models. No pipeline.
What it is
Pop-Search is a structured similarity & anomaly engine. You load your data as ordinary records, and it finds records that are similar to a given one, and records that are statistically out of place — using the fields you already have.
The whole idea in one line: vector-less search. A vector database needs you to train an embedding model, push every record through it, and accept a black box. Pop-Search skips all of that. Your numeric fields already carry meaning — price, rating, latency, temperature — so it works on them directly.
Every similarity result comes with a score you can read and a reason you can explain, which matters when the answer has to stand up in a pricing review, a fraud queue, or a maintenance ticket.
How it works
You load your data. Pop-Search indexes your numeric fields so that similarity, range filtering, and anomaly detection all run fast — even on a small 2-core / 4 GB server.
Load records as JSON or bulk CSV. Define which fields are numeric and which are categorical.
Pop-Search indexes your numeric fields for fast similarity, range, and anomaly lookups.
Search with ranges + filters, find similar records, and detect outliers — over HTTP, with interpretable results.
We can't share the internals just yet. The short version: it's deterministic, interpretable, and vector-less — and it's built to stay fast on modest hardware. This is a beta, open for public testing of the algorithm. What you see here is what we can show today; the details are on the way.
What you get
Nearest records by field proximity, each with an interpretable 0–1 score.
Statistical outliers per numeric field, with a z-score you can threshold and explain.
Numeric ranges + categorical equality, combined with AND / OR / must-not.
Group your matches by any field to see the shape of the result set.
Attach human-readable labels to your numeric fields and query by them.
Isolated collections per API key. One server, many customers.
One static binary. Local file persistence. No external database, no cluster.
Bulk CSV (optionally gzip) or streaming JSON. Update, delete, compact.
Why it's different
The tools people reach for today were built for a different job.
| Vector DB (Qdrant / Milvus) | Search engine (Elastic / Meili) | Postgres / SQL | Pop-Search | |
|---|---|---|---|---|
| Needs embeddings? | Yes | pgvector: yes | pgvector: yes | No |
| Needs a model / GPU | Yes | Optional | Optional | No |
| Numeric-field similarity | Via vectors | No | Hand-rolled SQL | Native |
| Anomaly / outlier detection | No | No | Hand-rolled | Native |
| Interpretable results | Black box | Text relevance | You define | Yes |
| Runs on 2-core / 4 GB | Hard | Yes | Yes | Yes |
Hardware
The entire engine is one static binary with local file persistence. It runs comfortably on a small VPS — the kind of box that hosts a dozen little services — with no JVM, no cluster, no external database, and no GPU.
That's the whole footprint — and there's still room for the rest of the stack.
Quick start
Base URL is the host serving this page (the API serves its own docs, so the playground and the API are the same origin).
With auth enabled, send your key in the X-Pop-Api-Key header.
1. Create a collection — declare your fields.
curl -s $BASE/collections -H 'Content-Type: application/json' -d '{
"name": "products",
"fields": {
"price": { "type": "numeric", "min": 0.99, "max": 10000, "bins": 20, "log": true },
"rating": { "type": "numeric", "min": 0, "max": 5, "bins": 16 },
"category": { "type": "categorical", "levels": ["electronics","furniture","outdoor","kitchen"] }
}
}'
2. Ingest records (JSON array, or POST /collections/products/ingest/csv for bulk).
curl -s $BASE/collections/products/records -H 'Content-Type: application/json' -d '[
{ "id": "e1", "price": 249.99, "rating": 4.6, "category": "electronics" },
{ "id": "e2", "price": 89.99, "rating": 4.2, "category": "electronics" }
]'
3. Search — numeric range + categorical filter, ANDed.
curl -s $BASE/collections/products/search -H 'Content-Type: application/json' -d '{
"must": [ { "field": "price", "gte": 50, "lte": 500 }, { "field": "category", "eq": "electronics" } ],
"limit": 20, "with_values": true
}'
4. Find similar — nearest records to a given one.
curl -s $BASE/collections/products/similar -H 'Content-Type: application/json' -d '{
"record": { "price": 249.99, "rating": 4.6, "category": "electronics" },
"k": 10, "with_values": true
}'
5. Detect anomalies — statistical outliers on a field.
curl -s $BASE/collections/products/anomalies -H 'Content-Type: application/json' -d '{
"field": "price", "z": 2.5, "limit": 50
}'
Live playground
No account, no setup. The button below loads a 45-product catalog into this server
(collection demo) and you query it live. It's the exact API from the docs — the playground just
calls it from your browser.
Pick a product; we return its nearest neighbours by field proximity (interpretable score).
Statistical anomalies on the price field. Three products are deliberately mispriced — can you catch all of them? Lower the z-threshold to widen the net.
Numeric range + categorical filter, ANDed — the core “range + AND/OR” query.
API reference
JSON in, JSON out. All query endpoints take a JSON body. Errors return a JSON
{ "error": "..." } with an appropriate HTTP status.
| Method | Endpoint | What it does |
|---|---|---|
| POST | /collections | Create a collection from a field spec |
| GET | /collections | List collections for the caller |
| GET | /collections/{name} | Collection stats (record count, per-field bins) |
| DEL | /collections/{name} | Delete a collection |
| POST | /collections/{name}/records | Ingest a JSON array of records |
| GET | /collections/{name}/records/{id} | Get one record by id |
| PATCH | /collections/{name}/records/{id} | Update a record's fields |
| DEL | /collections/{name}/records/{id} | Delete one record |
| POST | /collections/{name}/ingest/csv | Bulk CSV ingest (plain or gzip) |
| POST | /collections/{name}/search | Range + AND/OR/must-not query, facets, sort |
| POST | /collections/{name}/similar | Nearest records to a given record (scored) |
| POST | /collections/{name}/anomalies | Statistical outliers on a numeric field |
| PUT | /collections/{name}/labels | Set human labels for a numeric field's ranges |
| GET | /collections/{name}/labels | Get field labels |
| DEL | /collections/{name}/labels | Remove field labels |
| POST | /collections/{name}/relearn | Rebuild field stats after a data shift |
| POST | /collections/{name}/compact | Reclaim space from deleted records |
| GET | /health · /status · /metrics | Operational endpoints |
Query conditions support gte / lte (numeric range),
eq / neq / in (exact or set), label (field label), anomaly
(z-threshold), and exists (optional fields). Combine with must (AND), any (OR),
must_not (exclude).
Feedback