Agentic Search API Specification
Overview
The Agentic Search API facilitates deep, intent-focused search patterns. A user's query is comprehended by a generative AI agent, which designs a set of search queries around it. The agent retrieves items from a Marqo index and returns a collection of result categories. Clients can then render these categories and offer relevant options to the user.
The API streams responses in real-time, providing immediate feedback to the user. Developers can override features like the number of results per category, max number of categories, agent parameters, and search settings.
Note: Agentic Search is not publicly available at this time. Please contact us to request access.
Use Cases
This API is ideal for situations where a user’s query could span multiple, potentially non-overlapping product categories. The system interprets a high-level query and generates sub-queries ("head queries") that organize results by category.
Best suited for:
- Marqo users with diverse product catalogs
- Applications handling ambiguous or broad user queries
API Endpoint
Paths
GET /indexes/{index_name}/agentic-search
Authentication
- Include the
x-marqo-index-idheader (copy it from the Quick Start code snippets for your index in the Marqo Console). This is the credential to use from browsers and storefronts. - Server-side callers can send an API key in the
Authorization: Bearer {api_key}header instead. Do not expose your API key in browser code.
Request Format
Use GET with URL query parameters due to SSE constraints.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
payload | String | Yes | - | Base64-encoded JSON containing the request parameters (see below) |
stream | String | No | true | Must be true when present. The endpoint only supports streaming; stream=false returns 400 |
channel | String | No | - | Optional channel identifier for routing the request through a channel-specific agentic configuration. Use letters, numbers, hyphens, or underscores; maximum 64 characters. Channels are set up by Marqo for your index |
invalidateCache | String | No | false | Set to true to bypass the cached agentic response for this query and regenerate it. Case-insensitive |
invalidateCache=true forces a fresh generation on every request it is sent with, which costs latency and AI usage. Use it for testing, never on live storefront traffic.
Agentic Search Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
q | String | (required) | The user's query. Maximum 2048 characters by default |
categoryResultLimit | Integer | No fixed default | Number of results per category (distinct from searchSettings.limit). Must be 1 or greater. When omitted, the agent decides how many results to return per category |
maxCategories | Integer | 6 | Maximum number of categories the agent will attempt to return. Must be 1 or greater |
clickableSummary | Boolean | true | Defines if the summary will contain markdown content for interaction |
searchSettings | Dict | see below | Configuration used during search (see below) |
userId | String | null | Identifier for the shopper, recorded for analytics and attribution. It does not personalize results on this endpoint |
sessionId | String | null | Identifier for the browsing session, recorded for analytics and used to decide consistently whether a session receives a cached agentic response. It does not personalize results on this endpoint |
Unknown fields in the payload are ignored.
The 2048-character limit on q is the default. Marqo can configure a different limit for your index, and the 413 message reports the limit actually in force.
Clickable Summary
The agent will incorporate suggested follow-up queries in its summary. If this is set to true, the agent will implant this pattern in the summary: [[button:query=<your_query>]]. Custom markdown renderers can then display that text as a button or other element.
Search Settings
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | Integer | 10 | Maximum number of products to return. Must be 0 or greater |
offset | Integer | 0 | Number of products to skip (for pagination). Must be 0 or greater |
filter | String | null | Filter string using Marqo's query DSL to narrow search results |
attributesToRetrieve | List[String] | ["productTitle", "variantTitle", "price", "variantImageUrl", "collections", "_id", "_score"] | Specific product fields to return. If not specified, returns the default core product fields |
facets | Dict | null | Facet configuration for aggregated results |
sortBy | Dict | null | Sort configuration for results ordering |
language | String | null | Language passed to the underlying product searches |
profileId | String | null | Search profile to apply to the underlying product searches. Up to 64 characters; letters, numbers, hyphens, and underscores only |
Example Request
Step 1: Compose JSON payload (pretty-printed here for readability)
{
"q": "I need something to wear to a wedding",
"sessionId": "session-abc123",
"userId": "user-456",
"categoryResultLimit": 10,
"maxCategories": 4,
"clickableSummary": true,
"searchSettings": {
"limit": 24,
"offset": 0,
"filter": "availability:true",
"attributesToRetrieve": [
"productTitle",
"variantTitle",
"price",
"variantImageUrl",
"collections",
"_id",
"_score"
]
}
}
Step 2: Base64 encode this JSON
Encode the JSON as UTF-8, base64 the resulting bytes, then URL-encode the result when placing it in the query string. In JavaScript, btoa alone throws on any character above U+00FF, so encode the bytes first:
const bytes = new TextEncoder().encode(JSON.stringify(payload));
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
const payloadParam = encodeURIComponent(btoa(binary));
:::warning Non-ASCII queries are not handled correctly on this endpoint
This endpoint decodes the payload one byte per character instead of as UTF-8, so accented, non-Latin, and emoji characters in q reach the agent garbled and produce poor results. Only ASCII queries are decoded reliably. This is a known limitation of /agentic-search; conversational search decodes the same payload correctly and is unaffected.
If you already pass the JSON string straight to btoa, keep in mind that it throws on characters above U+00FF and that its output is not UTF-8, so it will stop matching this endpoint once the decoding is corrected. Contact Marqo before shipping a storefront that accepts non-ASCII queries against /agentic-search.
:::
The encoded payload for the example above:
eyJxIjoiSSBuZWVkIHNvbWV0aGluZyB0byB3ZWFyIHRvIGEgd2VkZGluZyIsInNlc3Npb25JZCI6InNlc3Npb24tYWJjMTIzIiwidXNlcklkIjoidXNlci00NTYiLCJjYXRlZ29yeVJlc3VsdExpbWl0IjoxMCwibWF4Q2F0ZWdvcmllcyI6NCwiY2xpY2thYmxlU3VtbWFyeSI6dHJ1ZSwic2VhcmNoU2V0dGluZ3MiOnsibGltaXQiOjI0LCJvZmZzZXQiOjAsImZpbHRlciI6ImF2YWlsYWJpbGl0eTp0cnVlIiwiYXR0cmlidXRlc1RvUmV0cmlldmUiOlsicHJvZHVjdFRpdGxlIiwidmFyaWFudFRpdGxlIiwicHJpY2UiLCJ2YXJpYW50SW1hZ2VVcmwiLCJjb2xsZWN0aW9ucyIsIl9pZCIsIl9zY29yZSJdfX0=
Step 3: Use in the URL query param payload
curl -G 'https://ecom.marqo-ep.ai/api/v1/indexes/my-first-index/agentic-search' \
--header 'x-marqo-index-id: abc123-my-first-index' \
--header 'Accept: text/event-stream' \
--data-urlencode "payload=$(echo -n '{
"q": "I need something to wear to a wedding",
"sessionId": "session-abc123",
"userId": "user-456",
"categoryResultLimit": 10,
"maxCategories": 4,
"clickableSummary": true,
"searchSettings": {
"limit": 24,
"offset": 0,
"filter": "availability:true"
}
}' | base64 | tr -d '\n')"
Error Responses
Errors returned before the stream starts use the standard error body {"error": "..."}:
403Agentic search is not enabled for this index400Query parameter 'payload' is required400Invalid base64 JSON payload400Query parameter 'stream' must be 'true' or 'false'400Only streaming mode is supported, please set the 'stream' parameter to 'true'400Invalid searchSettings.filter syntaxwhensearchSettings.filteris not valid filter DSL400with a validation message such asq: q is requiredwhen a payload field fails validation. The message is{field}: {reason}, so amaxCategoriesof0reports amaxCategories:message400Channel ID is required.,Channel ID must be at most 64 characters., orChannel ID must contain only alphanumerics, hyphens, and underscores.whenchannelis present but malformed413Query 'q' is too long (max 2048 characters).The number in the message is the limit configured for your index401Unauthorizedwhen neither credential is valid500Agentic config for channel "{channel}" is not configured.when the channel is well-formed but Marqo has not configured it for your index
Errors that occur after streaming has begun are delivered in-band; see Streaming Response.
Response
JSON Response Structure
| Field | Type | Description |
|---|---|---|
summary | String | AI-generated summary of the category results (may include links/buttons) |
categoryHits | List | List of result categories and associated items (see below) |
hits | List | Non-agentic search results using the provided query |
facets | Dict | Optional facets metadata (if configured) |
categoryHits Structure
| Field | Type | Description |
|---|---|---|
category | String | The category label |
confidence | Number | The agent's confidence score for this category (0 to 1) |
hits | List | Documents returned for this category query |
When automatic filtering is active, each category also carries appliedFilter, filterDropped, and originalFilter; see Automatic Filtering.
Streaming Response
- Delivered via Server-Sent Events (SSE).
- The HTTP status is
200as soon as the stream opens. Errors after that point arrive as events. - An
initevent indicates which features are enabled for this response. It is usually the first event, but the plain search runs in parallel with the agent, so adeltacarryinghitscan arrive beforeinit. Do not rely oninitbeing first. - Subsequent events send deltas (incremental updates) to the response.
- On success the final event is
stream-end. On an unrecoverable failure the final event iserrorand nostream-endfollows.
Event Types
| Event Type | Description |
|---|---|
init | Contains metadata about which features are enabled. |
delta | Incremental update containing partial results (summary, hits, categoryHits, facets), or a per-search failure (see below). |
error | The request failed after the stream opened. Always {"error": "Internal server error"}. The stream closes after this event. |
stream-end | Final event indicating the stream is complete. |
init Event Structure
| Field | Type | Description |
|---|---|---|
categories | Boolean | Indicates whether category hits will be streamed in this response |
summary | Boolean | Indicates whether an AI-generated summary will be streamed in this response |
Failures inside delta events
Individual searches can fail without ending the stream. If the plain search fails, a delta carries {"status": <http_status>, "error": "..."} instead of hits. If one category's search fails, the categoryHits entry for that category is {"category": "...", "status": <http_status>, "error": "..."} and has no hits. Other categories continue to stream.
Example Stream Output
event: init
data: {"categories": true, "summary": true}
event: delta
data: {
"summary": "Here"
}
event: delta
data: {
"summary": " are some options you might like for party wear."
}
event: delta
data: {
"hits": [
{"_id": "dress_001", "title": "Red Sequin Dress", "price": 89.99},
{"_id": "dress_005", "title": "Red Silk Gown", "price": 221.99}
]
}
event: delta
data: {
"categoryHits": [
{
"category": "Dresses",
"confidence": 0.9,
"hits": [
{"_id": "dress_001", "title": "Red Sequin Dress", "price": 89.99},
{"_id": "dress_002", "title": "Black Silk Gown", "price": 129.99},
{"_id": "dress_003", "title": "Blue Off-Shoulder Dress", "price": 99.99}
]
}
]
}
event: delta
data: {
"summary": "Try searching for [[button:query=Casual party outfits]], [[button:query=Formal party attire]] or [[button:query=Accessories for a party]] for more options."
}
event: delta
data: {
"categoryHits": [
{
"category": "Shoes",
"confidence": 0.8,
"hits": [
{"_id": "shoe_001", "title": "Silver Heels", "price": 59.99},
{"_id": "shoe_002", "title": "Strappy Sandals", "price": 49.99}
]
}
]
}
event: stream-end
data: {}
Automatic Filtering
When automatic filtering is enabled for your index, the AI agent can apply precise filters to search results based on user intent. The agent understands the product attributes available in your catalog (brands, price ranges, categories, etc.) and constructs filters automatically when the user's query implies specific criteria.
How It Works
-
Filter construction: When user intent is clear, the agent constructs filter expressions using Marqo's filter DSL:
- Exact match:
brand:(Nike) - Multiple values:
(brand:(Nike) OR brand:(Adidas)) - Numeric range:
price:[50 TO 100] - Open-ended range:
price:[* TO 100] - Combined:
brand:(Nike) AND price:[* TO 100]
- Exact match:
-
Filter merging: The agent's filter is merged with any client-provided
searchSettings.filterusing AND. For example, if the client sendsfilter: "availability:true"and the agent constructsbrand:(Nike), the final filter becomes(availability:true) AND (brand:(Nike)). -
Zero-hit fallback: If the merged filter yields no results, the agent's filter is automatically dropped and the search retries with only the client-provided filter. This ensures users always see relevant products even when the agent's filter is too restrictive.
Response Metadata
When automatic filtering is active, each category in the categoryHits response includes filter metadata:
| Field | Type | Description |
|---|---|---|
appliedFilter | string | The filter that was actually used for the search |
filterDropped | boolean | true if the agent's filter was dropped due to zero results |
originalFilter | string | The original filter before fallback (present when filterDropped is true) |
{
"categoryHits": [
{
"category": "Running Shoes",
"confidence": 0.9,
"hits": ["..."],
"appliedFilter": "(availability:true) AND (brand:(Nike))",
"filterDropped": false
}
]
}