AACSearch

Search

GET and POST /api/v1/indexes/{slug}/search — every query parameter, the response body and the headers that come with it.

GET  /api/v1/indexes/{slug}/search
POST /api/v1/indexes/{slug}/search

Both methods run the same query; GET takes query-string parameters and POST takes a JSON body, which is the better fit for long filter_by expressions, vector queries and non-ASCII text. Requires scope search:read, consumes one search unit from the plan quota.

Quick example

curl -sS -G "$AAC_API/indexes/documentation/search" \
  -H "Authorization: Bearer $AAC_KEY" \
  --data-urlencode "q=pricing plans" \
  --data-urlencode "query_by=title,content" \
  --data-urlencode "query_by_weights=4,2" \
  --data-urlencode "filter_by=tags:=[billing]" \
  --data-urlencode "facet_by=tags" \
  --data-urlencode "sort_by=_text_match:desc,published_at:desc" \
  --data-urlencode "per_page=5"
curl -sS -X POST "$AAC_API/indexes/documentation/search" \
  -H "Authorization: Bearer $AAC_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "pricing plans",
    "query_by": "title,content",
    "filter_by": "tags:=[billing]",
    "facet_by": "tags",
    "per_page": 5
  }'

Response

{
	"index": "documentation",
	"query": "pricing plans",
	"language": "en",
	"found": 2,
	"out_of": 137,
	"page": 1,
	"per_page": 5,
	"took_ms": 18,
	"engine_ms": 4,
	"hits": [
		{
			"id": "pricing",
			"score": 2711,
			"document": {
				"id": "pricing",
				"external_id": "pricing",
				"title": "Pricing",
				"content": "Free includes 10,000 searches per month. Pro is 29 USD per month…",
				"url": "https://example.com/pricing",
				"source_type": "api",
				"organization_id": "org_01h9",
				"tags": ["billing"],
				"indexed_at": 1761214364
			},
			"highlights": [
				{ "field": "title", "snippet": "<mark>Pricing</mark>", "matchedTokens": ["pricing"] },
				{
					"field": "content",
					"snippet": "…Pro is 29 USD per <mark>month</mark>…",
					"matchedTokens": ["month"]
				}
			]
		}
	],
	"facets": [
		{
			"field": "tags",
			"values": [
				{ "value": "billing", "count": 2, "highlighted": "billing" },
				{ "value": "api", "count": 1 }
			]
		}
	],
	"request_id": "req_9a7c3d1e5f0b2a4c6d8e0f1a"
}
FieldMeaning
languageLanguage detected from q, used for stemmer hints
foundMatches for this query, after filters
out_ofDocuments scanned in the collection
took_msPlatform time including quota and logging
engine_msTime reported by the search service
hits[].scoreText-match score; higher is better
hits[].highlightsSnippets with <mark> tags applied by the engine
grouped_byPresent only when group_by was used: { field, groups[] }

Empty result sets are a 200 with found: 0 and an empty hits array. They are logged as zero-result queries.

Parameters

All parameters are optional except q for POST (defaults to an empty string, which the engine treats as match-all).

ParameterTypeNotes
qstring ≤ 1000Query text. Empty means "browse everything" (* internally)
profilestring ≤ 64Saved search profile whose settings act as defaults
query_bystringComma-separated fields, default title,content,tags
query_by_weightsstringOne weight per query_by field, e.g. 4,2,1; count must match
filter_bystring ≤ 2000Filter expression; may not reference organization_id
sort_bystring ≤ 200field:asc,field2:desc; _text_match:desc is allowed
facet_bystringComma-separated facet fields
max_facet_valuesint 1–50Default 10, capped by the deployment
facet_querystringtags: frontend — facet search with facet_query_num_typos (0–3)
pageintDefault 1, maximum 100 (SEARCH_MAX_DEEP_PAGE); a larger value is rejected with 422 VALIDATION_ERROR, not clamped. Use offset for deeper scans
per_pageintDefault 10; clamped to the deployment cap (SEARCH_MAX_PER_PAGE, 250 by default) rather than rejected
offsetintAlternative to page
limit_hitsintCap on total hits considered
num_typosint 0–3Default 2
min_len_1typo / min_len_2typoint 1–20Word length before a typo is allowed
typo_tokens_threshold / drop_tokens_thresholdint 0–10Fallbacks for long queries
prefixbooleanDefault true; set false for exact-prefix-free matching (semantic search forces it off)
prioritize_exact_matchbooleanPrefer exact tokens over typo matches
exhaustive_searchbooleanTrade latency for completeness
search_cutoff_msint 0–60000Abort the engine query after this budget
group_by / group_limitstring / int 1–50One field, one result group per value
highlight_fields, highlight_full_fieldsstringFields to excerpt
highlight_start_tag, highlight_end_tagstring ≤ 20Replace <mark> with your own tags
snippet_thresholdint 1–500Snippet length around the match
include_fields, exclude_fieldsstringProjection of the returned document
vector_querystringembedding:([0.1,0.2], k:10, alpha:0.6) for explicit kNN
semanticbooleanHybrid search over the index auto-embedding field
alphafloat 0–1Vector weight when semantic=true; 0 is keyword-only, 1 is vector-only
aclstring[] ≤ 32Principals user:<id>, group:<id> or role:<id> for per-document access control. Honoured only for a server key without allowedOrigins and a request without an Origin header; otherwise ignored and the search runs as anonymous (public documents only)

Booleans accept true/false and 1/0. Values that fail validation produce 422 VALIDATION_ERROR with the Zod issue list; values that are valid but exceed an engine cap are clamped:

{
	"error": {
		"code": "VALIDATION_ERROR",
		"message": "Request payload is invalid",
		"request_id": "req_3c7a1f5b9d2e4c6a8b0d2f4e",
		"details": {
			"issues": [
				{
					"code": "too_small",
					"path": ["num_typos"],
					"message": "Number must be greater than or equal to 0"
				}
			]
		}
	}
}

semantic=true rewrites the request for an index that has an auto-embedding field: the embedding field is appended to query_by, prefix is forced to false (remote embedders cannot prefix-match) and a vector_query with k equal to the page size is added. alpha sets the blend.

curl -sS -G "$AAC_API/indexes/documentation/search" \
  -H "Authorization: Bearer $AAC_KEY" \
  --data-urlencode "q=how much does the pro plan cost" \
  --data-urlencode "semantic=true" \
  --data-urlencode "alpha=0.6" \
  --data-urlencode "per_page=3"

If the index has no auto-embedding field the request fails with 400 INVALID_QUERY and the message Index "…" has no auto-embedding field. semantic also requires the hybridSearch plan feature; explicit vector_query requires vectorSearch. See Semantic search.

Saved profiles and A/B

profile=<slug> applies a saved profile's query_by, weights, facets, sort and paging as defaults; anything you pass explicitly wins. An unknown slug is 404 PROFILE_NOT_FOUND. When the index has settings.abTest enabled and no profile is passed, the platform picks a profile deterministically from the key and index — the same key always lands in the same bucket, which is what makes the comparison in /analytics/experiments meaningful. See A/B experiments.

Headers on the response

x-aac-quota-limit: 500000
x-aac-quota-remaining: 499996
x-aac-index: documentation
x-aac-profile: default
x-aac-charged-cents: 0
x-ratelimit-limit: 600
x-ratelimit-remaining: 412
x-ratelimit-reset: 1761214424

x-aac-charged-cents is non-zero only when the plan allowance is exhausted and the wallet covered the block.

These are part of the same surface and share the same auth model:

EndpointScopePurpose
GET /indexes/{slug}/statssearch:readDocument count, fields, 24h query volume and average latency
POST /indexes/{slug}/eventsanalytics:writeRecord a click or conversion
GET /indexes/{slug}/widget-configsearch:readSearch profiles available to the index
POST /indexes/multi_searchsearch:readFederated search: indexes are named in the body
GET/PUT /indexes/{slug}/synonyms; DELETE /indexes/{slug}/synonyms/{id}settings:writeOne-way and multi-way synonyms
GET/PUT /indexes/{slug}/curations; DELETE /indexes/{slug}/curations/{id}settings:writePin, hide or rewrite specific queries
POST /indexes/{slug}/reindexsettings:writeZero-downtime rebuild
curl -sS "$AAC_API/indexes/documentation/stats" \
  -H "Authorization: Bearer $AAC_KEY"
{
	"index": {
		"slug": "documentation",
		"collection": "aac_org_01h9_documentation",
		"documents": 137,
		"fields": [
			{
				"name": "title",
				"type": "string",
				"facet": false,
				"optional": true,
				"index": true,
				"sort": false,
				"infix": false
			},
			{
				"name": "tags",
				"type": "string[]",
				"facet": true,
				"optional": true,
				"index": true,
				"sort": false,
				"infix": false
			}
		],
		"created_at": "2026-10-01T08:20:11.000Z"
	},
	"queries_24h": 1841,
	"avg_latency_24h_ms": 17.42,
	"usage": {
		"plan": "pro",
		"period": "2026-10",
		"resetsAt": "2026-11-01T00:00:00.000Z",
		"searches": { "used": 4120, "limit": 500000 },
		"documents": { "used": 137, "limit": 50000 }
	},
	"request_id": "req_6e1b8d0a2c4f6a8d0b2e4c6a"
}

On this page