Introduction
The Search API exposes the Heurix engine: a search engine built for technical catalogs, where queries and product records go through the same pipeline — normalization (case, accents), business synonyms, typo tolerance, then a rule cascade that turns text into structured annotations. That cascade is what lets m8x20 find a record stored as "M8 x 20 — A2".
The API is REST, JSON, HTTPS only. All responses are UTF-8 encoded.
Quickstart
Three calls are enough for a working first search.
1 Index your products (the catalog is created automatically on first send):
curl -X POST https://api.heurix.fr/v1/index/mycatalog/items \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rulepack": "outillage",
"items": [
{"id": "V1", "name": "Hex head screw", "ref": "M8 x 20 — A2", "stock": 100},
{"id": "V2", "name": "Hex head screw", "ref": "M8 x 30 — A2", "stock": 50}
]
}'
2 Search — typos are welcome:
curl -X POST https://api.heurix.fr/v1/index/mycatalog/search \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"q": "m8x20 stainless"}'
3 Read the response — every result explains why it matched:
{
"query": "m8x20 stainless",
"total": 2,
"hits": [
{
"product": {"id": "V1", "name": "Hex head screw", "ref": "M8 x 20 — A2", "stock": 100},
"score": 37.5,
"in_stock": true,
"matched": ["annotation #VIS_M8X20", "annotation #DIAM_M8", "annotation #LONG_20"]
}
]
}
Authentication
Every call must carry your API key in the Authorization header:
Authorization: Bearer hx_your_api_key
A missing or malformed key returns 401; an invalid key returns 403. Keep your key server-side — never expose it in your site's public JavaScript. If your key leaks, contact us for immediate rotation.
Request counting
Billing is based on a monthly request counter, simple by design:
| Search | 1 search call = 1 request |
| Indexing | never counted against your request quota — update your catalog as often as needed, only the total number of indexed products is capped by plan |
| Federated search | 1 request per catalog queried |
| Management | Every other API call (synonyms, stats, deletion...) = 1 request |
| Overage (Scale) | Extra usage is billed at the rate shown on the pricing page — never a cutoff |
| Overage (other plans) | A 10% grace margin absorbs a one-off spike; beyond that, the request is rejected with a clear 429 code |
Your current usage is available anytime via GET /v1/usage.
Plans and limits
Every API key is tied to a plan that defines its monthly request volume, its indexable product count, and its number of catalogs. Feature details for each plan are on the pricing page; here are the raw limits:
| Plan | Requests / month | Max products | Catalogs |
|---|---|---|---|
| Trial (14 days) | 2,000 | 2,000 | 2 |
| Starter — €19/month | 15,000 | 8,000 | 1 |
| Growth — €49/month | 30,000 | 25,000 | 3 |
| Scale — €139/month | 150,000 then billed | 100,000 | unlimited |
Past the 14-day trial without subscribing to a paid plan, a key automatically reverts to trial limits until subscription — your catalogs and their configuration remain intact, simply less accessible in volume until you move to a paid plan.
Exceeding a limit returns a 429 code with the detail of the cause:
{
"detail": "Starter plan request limit exceeded (5,512/5,000, 10% grace margin also exceeded). Upgrade to a higher plan to continue.",
"plan": "starter",
"limit_type": "requests",
"upgrade_url": "https://heurix.fr/pricing.html"
}
Product structure
A product is a free-form JSON object. Four fields play a special role:
| Field | Type | Role |
|---|---|---|
id | string required | Unique identifier within the catalog. Re-indexing the same id replaces the product (upsert). |
ref | string | Product reference (SKU, manufacturer code). Indexed, highest weight. |
name | string | Product name. Indexed, high weight. |
description | string | Description. Indexed, standard weight. |
stock | number | bool | string | Availability — used for sorting at equal relevance. 0, false, "out" = unavailable. |
Any other field (price, image, URL, category...) is stored as-is and returned in results, without being indexed.
/v1/index/{catalog}/searchSearch
Runs a search on a catalog. Results sorted by relevance, then availability at equal score. An empty query (q: "") is accepted: that's browse mode, for navigating a catalog by facets alone, before any text input.
Request body
| Parameter | Type | Description |
|---|---|---|
q | string | The search query (0 to 500 characters — empty allowed, see browse mode above). |
limit | integer | Results per page (1–100, default 10). |
offset | integer | Pagination offset (default 0). |
facets | array of string | Annotation groups to return counts for (e.g. ["PAYS", "FORMAT"]). See Facets and filters. |
filters | array of string | Exact annotations required, combined with AND (e.g. ["PAYS_SE", "FORMAT_PO"]). |
Response
total (match count), hits — each hit contains the full product as indexed, its score, in_stock, and matched: a readable list of why it matched (terms found, typos corrected, shared annotations) — useful for understanding and tuning relevance. If facets was requested, an additional facets field lists the counts per group.
Zero-result fallback
A search that finds nothing (total: 0) on a catalog containing at least one product marked featured: true at indexing time returns up to 6 of these products in hits, with fallback: true — to offer a way forward instead of a blank page. total stays 0: these aren't real search results, so present them distinctly on your end (e.g. "No results for '…' — our picks" rather than blending them with real results). Without any featured product in the catalog, hits stays empty and fallback is false, as before.
Facets and filters
A facet groups the annotations that share a common prefix — PAYS_SE and PAYS_FR belong to the PAYS group. Request a per-group count with facets, narrow results with filters:
curl -X POST https://api.heurix.fr/v1/index/mycatalog/search \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"q": "crime", "facets": ["PAYS", "FORMAT"], "filters": ["PAYS_SE"]}'
{
"total": 12,
"hits": [ ... ],
"facets": {
"PAYS": {"PAYS_SE": 12, "PAYS_FR": 34, "PAYS_UK": 9},
"FORMAT": {"FORMAT_PO": 8, "FORMAT_BR": 4}
}
}
Counting is disjunctive within a single group: filtering on PAYS_SE doesn't make PAYS_FR disappear from the count — that's what lets you show "France (34)" as an alternative option even once Sweden is selected, exactly like a standard e-commerce filter sidebar. Across different groups, however, filters remain cumulative (logical AND).
/v1/federated-searchFederated search
Queries multiple catalogs in a single call and merges results by relevance — useful for a multilingual site (one catalog per language) or multi-brand setup. Scoring weights are global to the engine, not per-catalog: scores stay comparable across catalogs, even with different rule packs.
Request body
| Parameter | Type | Description |
|---|---|---|
catalogs | array of string required | 1 to 10 catalog names to query. |
q | string | The search query. |
limit / offset | integer | Pagination over the merged set. |
Response
Same shape as a regular search, with a catalog field added to each hit to identify its source, and catalogs_searched / catalogs_not_found to spot an invalid catalog name.
curl -X POST https://api.heurix.fr/v1/federated-search \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"catalogs": ["store-fr", "store-en"], "q": "red sweater"}'
Counting: 1 request per catalog actually queried — federating across 3 catalogs counts as 3 requests, same principle as batch indexing.
/v1/index/{catalog}/itemsIndexing
Adds or updates products in batch (upsert on id). The catalog is created automatically if it doesn't exist. Optional featured field (boolean): marks a product as a candidate for the fallback shown when a search finds nothing — see Search, zero-result fallback.
Request body
| Parameter | Type | Description |
|---|---|---|
items | array required | 1 to 5,000 products per call. Every product must have an id. |
rulepack | string | Rule pack to attach to the catalog (outillage, mode, industrie). Changing the pack re-indexes the catalog. |
Response
{"indexed": 2, "catalog": {"catalog": "mycatalog", "products": 2, "terms": 11,
"annotations": 9, "rulepack": "outillage", "synonym_groups": 0}}
/v1/index/{catalog}/items/{id}Deletion
Removes a product from the index and storage. Returns 404 if the id is unknown.
curl -X DELETE https://api.heurix.fr/v1/index/mycatalog/items/V1 \
-H "Authorization: Bearer YOUR_API_KEY"
/v1/index/{catalog}/synonymsSynonyms
Business synonyms expand search: "screw" can find "bolt". GET reads the current groups, PUT replaces them entirely. Groups sharing a term are merged automatically.
curl -X PUT https://api.heurix.fr/v1/index/mycatalog/synonyms \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"groups": [["screw", "bolt"], ["nut", "ecrou"]]}'
/v1/index/{catalog}/statsStatistics
Catalog state: number of products, indexed terms, annotations produced by the cascade, active rule pack, synonym groups.
/v1/usageUsage
Request counter for the current month, for the calling key.
{"month": "2026-07", "requests": 1284}
/v1/rulepacksRule packs
Lists the packs available on your instance, with their level and rule counts. Three standard packs ship by default: outillage (fasteners, DIN/ISO standards, materials), mode (sizes, colors, materials, families), industrie (bearings, standardized references). Custom packs for your catalog can be designed as a ROI Simulator mission.
/healthService health
Liveness, no authentication — handy for your monitoring probes.
{"status": "ok", "catalogs": 3}
The annotation cascade
This is the engine's core. A rule pack is organized in levels: level 1 applies regular expressions to normalized text and produces annotations; the next levels apply to the annotation stream and compose them. Documents and queries go through the same cascade, so matching happens in a shared space:
"m8x20 stainless" "M8 x 20 — A2" (product record)
│ │
▼ level 1 ▼ level 1
DIAM_M8, LONG_20, MAT_INOX DIAM_M8, LONG_20, MAT_INOX
│ │
▼ level 2 ▼ level 2
VIS_M8X20 ──────────── match ───────────── VIS_M8X20
Every shared annotation weighs heavily in the score — that's what puts the right reference first even when the written form differs. The matched field of each result makes those annotations visible.
Good practices
Fill ref, name, and description separately. Field weighting is what makes ranking good — concatenating everything into name flattens relevance.
Keep references as they are. Don't "clean" product references before indexing (stripping spaces, dashes...): normalization and the cascade handle it, and the original form stays available for display.
Index in batches of 1,000 to 5,000. One batch = one network call; too small multiplies calls, the cap is 5,000 per call.
Send stock with every update. Tie-break sorting relies on it; up-to-date stock avoids promoting unavailable products.
One catalog per language. If your store exists in French and English, create store-fr and store-en — synonyms and rules stay consistent per language.
Use matched to tune relevance. Every result explains why it ranks: if a product surfaces wrongly, this field shows which term or annotation made it match — it's your diagnostic tool.
Error codes
| Code | Meaning | What to do |
|---|---|---|
401 | Authorization header missing or malformed | Send Authorization: Bearer <key> |
403 | Invalid API key | Check the key; contact us if it was revoked |
404 | Catalog or product not found | Check the catalog name and id — the catalog is created on first indexing call, not before |
422 | Invalid request body | The error detail names the field at fault (e.g. product missing an id) |
5xx | Server-side error | Retry; if it persists, write to contact@heurix.fr |
Limits
| Search query | 500 characters maximum |
| Results per page | 100 maximum (limit) |
| Indexing | 5,000 products per call — send multiple batches for a larger catalog |
| Synonym groups | 2,000 per catalog |
| CORS | Browser calls are not open: call the API from your server, not from public JavaScript (your key would be exposed) |
Need more than these limits? Let's talk — that's what the Scale plan and dedicated setups are for.