Hybrid Search API
One call returns keyword results and starts an AI answer over the same catalog. It is the endpoint behind the Hybrid Search SDK module.
This is the search to use for content. Articles, video and documents bring readers who ask in sentences and want an answer, not only a list. For a product catalog, where traffic is heavy and every query must render at once, use the Search API instead.
Base URL https://api.askmiso.com. Authenticate with the X-API-KEY
header, or ?api_key=. See Authentication.
Search (+ answer)
POST /v1/ask/search
| Field | Type | Default | Notes |
|---|---|---|---|
q |
string | required | The user's query. |
answer |
boolean | "AUTO" |
true |
Run the answer pipeline. "AUTO" decides per query. |
rows |
integer | 10 |
Number of search results. |
start |
integer | 0 |
Result offset. |
fl |
string[] | ["title"] |
Product fields to return (any uploaded field, incl. custom_attributes). |
source_fl |
string[] | ["title"] |
Fields returned for answer sources. |
stemming |
boolean | true |
Reduce query words to their root form. |
facets |
(string | object)[] | [] |
Fields to count, such as tags. Returned in facet_counts. See below. |
order_by |
string | object[] | relevance |
relevance, published_at (newest first), -published_at (oldest first), or your own fields. See below. |
fq |
string | — | Filter, for example tags:Computing. See Filter Syntax (fq). |
user_id / anonymous_id |
string | — | For personalization. |
curl -X POST "https://api.askmiso.com/v1/ask/search" \
-H "X-Api-Key: YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"q": "interest rate cuts 2026",
"answer": "AUTO",
"rows": 5,
"fl": ["title", "url", "cover_image"],
"fq": "section:\"markets\""
}'
{
"message": "success",
"data": {
"took": 731,
"total": 3,
"question_id": "064bda22-1e30-4411-b735-9370a8e48aa4",
"miso_id": "85d792d9-50a2-43dc-9…",
"products": [
{
"product_id": "wikinews-2911293",
"title": "Gregory Kurtzer discusses plans for Rocky Linux with Wikinews…",
"url": "https://en.wikinews.org/wiki/Gregory_Kurtzer_discusses_plans…"
}
],
"facet_counts": {
"facet_fields": {
"tags": [["Computing", 2], ["FLOSS", 2]]
}
},
"parsed_query": null,
"suggestion": null
}
}
The answer is not in this response. You get question_id, and you poll the
same endpoint the Ask module uses:
POST /v1/ask/search -> products now, plus question_id
GET /v1/ask/questions/{question_id}/answer -> the answer, when finished
The search returns in about 2 seconds, and the answer follows about 5 seconds later with 7 sources. Render the results first, then fill the answer in when it arrives.
| Field | What it is |
|---|---|
took |
Search time in milliseconds. |
total |
Matching products, before rows is applied. |
products |
The results. fl decides which fields each one carries. |
question_id |
Poll this for the answer. |
facet_counts.facet_fields |
Counts per value, for each field in facets. |
miso_id |
The request id. Send it back on an interaction to attribute the click. |
parsed_query, suggestion |
Query understanding and a spelling suggestion. null when there is nothing to report. |
The search and the answer come back separately. Render the results as soon as they arrive, and let the answer appear when it is ready.
How Miso reads the query
q is a search query, not a sentence Miso guesses at. These operators work,
and the counts below come from a catalog of 20,901 news articles.
| You write | You get | Matched |
|---|---|---|
linux kernel |
Both words. Miso joins bare words with AND. | 8 |
"linux kernel" |
The exact phrase, in that order. | 5 |
linux AND kernel |
The same as the first row. | 8 |
software OR hardware |
Either word. | 438 |
kernel NOT linux |
The first word, without the second. | 8 |
comput* |
Any word that starts with comput. |
958 |
Two details are worth knowing:
-
A bare space means AND.
linux kernelandlinux AND kernelboth return 8. A reader who types more words gets fewer results, not more. -
NOTsubtracts.kernelalone matches 16 articles, 8 of which also saylinux, sokernel NOT linuxleaves 8. - Quotes narrow.
"open source"returns 100, the loose form 317.
Quotes are repaired, not rejected
Miso normalizes the query before it runs:
| Input | What Miso does |
|---|---|
“linux kernel” |
Curly quotes become straight ones. It matched the same 5 articles as the straight form. |
"linux kernel |
An odd number of quotes: the unmatched one is removed. It matched 8, the same as no quotes. |
AT&T, m&a, west-bank |
A word joined by &, - or @ is quoted for you, so it survives as one term. |
| CJK text | Miso adds the phrase boundaries, because the words carry no spaces. |
A malformed query therefore returns results rather than an error.
Order the results
order_by takes a shorthand string, or a list of objects for your own fields.
The default is relevance.
| Value | Order |
|---|---|
relevance |
Best match first. The default. |
published_at |
Newest first. |
-published_at |
Oldest first. |
The minus sign does not mean descending here.
published_atis already newest first, and-published_atreverses it to oldest first. It reads backwards, so check it against your own data before you ship a sort control.
Against a catalog of 20,901 news articles:
2023-05-01 Microsoft, Nware sign 10-year cloud gaming deal
2023-03-09 Netherlands set to further restrict semiconductor technology exports
2021-07-05 Ransomware attack hits over 200 US companies
2004-11-15 Big Linux Beta 3 released
2004-11-16 Longhorn for 2006, according to Gates
2004-11-29 Lycos launches screensaver to increase spammers' bills
A record with no published_at sorts by updated_at. It is not pushed to
the end. In the run above, three undated articles appeared in the middle of the
list, each in the position its updated_at earned.
Sort on your own fields
Send a list instead of a string. Each entry names a field, and Miso applies them in order:
{
"q": "technology",
"order_by": [
{
"field": "custom_attributes.word_count"
},
{
"field": "_search_score"
}
]
}
| Key | What it does |
|---|---|
field |
Any numeric or boolean field in your catalog, or _search_score, or published_at. |
default_value |
The value to use when a record does not have that field. Default 0.0. |
tie_breaker |
What to sort on when two records tie. |
Facet the results
facets returns counts per value, for the products that match the query. Use
them to build filters.
The short form is a list of field names:
{
"q": "technology",
"facets": ["tags"]
}
{
"facet_counts": {
"facet_fields": {
"tags": [
["Science and technology", 1807],
["United States", 1010],
["North America", 903],
["Europe", 484]
]
}
}
}
Each entry is [value, count], sorted by count, largest first. The default is
the 10 most common values.
Control the facet
The long form is an object:
| Key | What it does |
|---|---|
field |
The field to count. A custom attribute uses its dotted path. |
size |
How many values to return. Default 10. |
alias |
The key to use in the response. Needed when you facet one field twice. |
include |
A regular expression. Keep only values that match. |
exclude |
A regular expression. Drop values that match. |
ranges |
Buckets for a numeric or date field, each with a key. |
{
"facets": [
{
"field": "tags",
"alias": "topics",
"size": 3
}
]
}
{
"facet_counts": {
"facet_fields": {
"topics": [
["Science and technology", 1807],
["United States", 1010],
["North America", 903]
]
}
}
}
The alias replaces the field name as the key, so two facets on one field do not collide.
include and exclude are case sensitive, and they match anywhere in the
value unless you anchor them:
{
"facets": [
{
"field": "tags",
"size": 5,
"include": "Comp.*"
}
]
}
That returns Computing (332), Computron (Wikinewsie) (6), Computer Fraud and Abuse Act (3), and Computron (WWC2013) (2). Neither option changes the
search results, only the facet values.
Range facets
Group a numeric or date field into buckets you name:
{
"facets": [
{
"field": "custom_attributes.word_count",
"ranges": [
{
"to": 200,
"key": "short"
},
{
"from": 200,
"to": 600,
"key": "medium"
},
{
"from": 600,
"key": "long"
}
]
}
]
}
{
"facet_counts": {
"facet_fields": {
"custom_attributes.word_count": [["medium", 1495], ["long", 538], ["short", 535]]
}
}
}
from is inclusive, to is exclusive, and each bucket needs at least one of
them. The response uses your key, and still sorts by count.
