AACSearch

RAG assistant

Grounded answers with citations, a stateful conversation assistant with streaming, and the engine-native conversation endpoint.

The platform offers three ways to turn search results into prose, all grounded in the tenant's own index:

EndpointShapeState
POST /indexes/{slug}/answerOne question in, one answer plus citations outStateless
POST /indexes/{slug}/assistantConversation turn, history persistedStateful, conversation_id
POST /indexes/{slug}/assistant/nativeEngine conversation model, platform-managed historyStateful, engine-managed

All three retrieve with the same tenant-scoped search, so the model never sees another organization's documents — retrieval has already been filtered before the prompt is built.

One-shot answers

curl -sS -X POST "$AAC_API/indexes/documentation/answer" \
  -H "Authorization: Bearer $AAC_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "question": "How much does the pro plan cost and what is included?", "per_page": 4 }'
{
	"index": "documentation",
	"answer": "Pro costs 29 USD per month and includes 500,000 searches [1].",
	"citations": [
		{
			"index": 1,
			"id": "pricing",
			"title": "Pricing",
			"url": "https://example.com/pricing",
			"snippet": "Pro is 29 USD per month with 500,000 searches."
		}
	],
	"found": 4,
	"model": "gpt-4o-mini",
	"tokens": 218,
	"took_ms": 640,
	"request_id": "req_2c4e6a8b0d2f4a6c8e0b2d4f"
}

citations[].index matches the [n] markers in the answer text, so a UI can render numbered footnotes without parsing the prose.

Behaviour worth knowing:

  • Retrieval runs per_page of at most 6 documents (the request's per_page is capped at 10, the context at 6).
  • No matches is not an error. You get found: 0, empty citations and the sentence No documents matched this question, so there is nothing to summarise. — a truthful empty answer beats a hallucinated one.
  • answer requires an AI provider: without OPENAI_API_KEY it is 503 AI_NOT_CONFIGURED.
  • It needs the aiAnswer plan feature, which every plan includes — Free too, with a small hard-capped allowance of 10,000 AI tokens per month (no overage: past it you get 429 QUOTA_EXCEEDED).
  • AI tokens are metered against the ai_tokens allowance.

Any search parameter can accompany question — filter_by and query_by are the useful ones:

{
	"question": "what changed in 1.8?",
	"filter_by": "tags:=[changelog]",
	"query_by": "title,content"
}

Conversation assistant

curl -sS -X POST "$AAC_API/indexes/documentation/assistant" \
  -H "Authorization: Bearer $AAC_KEY" \
  -H "Content-Type: application/json" \
	-d '{ "question": "How much does pro cost?", "conversation_id": "conv_01HZYB" }'
{
	"conversation_id": "conv_01HZYB",
	"answer": "Pro is 29 USD per month with 500,000 searches [1].",
	"mode": "llm",
	"model": "gpt-4o-mini",
	"citations": [
		{
			"n": 1,
			"id": "pricing",
			"title": "Pricing",
			"url": "https://example.com/pricing",
			"snippet": "Pro is 29 USD per month with 500,000 searches."
		}
	],
	"found": 4,
	"tokens": 231,
	"latency_ms": 702,
	"request_id": "req_8b0d2f4a6c8e0b2d4f6a8c0e"
}

Omit conversation_id to start a thread; the response's id goes into the next call. The id is also returned in the x-aac-conversation header. Messages are persisted and metered as ai_tokens.

mode tells you how the answer was produced:

  • llm — a model answered from the retrieved sources (requires OPENAI_API_KEY).
  • extractive — no model available, so the assistant returned a structured summary of the matching snippets with a source list. The endpoint still works; the prose is just assembled rather than generated.

Read the history back:

curl -sS "$AAC_API/indexes/documentation/assistant" -H "Authorization: Bearer $AAC_KEY"
{
	"conversations": [
		{
			"id": "conv_01HZYB",
			"title": "How much does pro cost?",
			"messageCount": 2,
			"createdAt": "2026-10-23T10:12:00.000Z",
			"updatedAt": "2026-10-23T10:12:04.000Z"
		}
	]
}
curl -sS "$AAC_API/indexes/documentation/assistant/conv_01HZYB" \
  -H "Authorization: Bearer $AAC_KEY"
{
	"conversation": {
		"id": "conv_01HZYB",
		"title": "How much does pro cost?",
		"created_at": "2026-10-23T10:12:00.000Z",
		"messages": [
			{
				"id": "msg_1",
				"role": "user",
				"content": "How much does pro cost?",
				"citations": [],
				"created_at": "2026-10-23T10:12:00.000Z"
			},
			{
				"id": "msg_2",
				"role": "assistant",
				"content": "Pro is 29 USD per month…",
				"citations": [{ "n": 1, "id": "pricing", "title": "Pricing" }],
				"created_at": "2026-10-23T10:12:04.000Z"
			}
		]
	}
}

A conversation id that belongs to another index is 404 CONVERSATION_NOT_FOUND — the lookup is scoped by index, not just by tenant.

Streaming

curl -N -sS -X POST "$AAC_API/indexes/documentation/assistant/stream" \
  -H "Authorization: Bearer $AAC_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "question": "And what are the limits?" }'
data: {"type":"citations","payload":{"citations":[...],"conversation_id":"conv_01HZYB"}}

data: {"type":"delta","payload":{"text":"Each API key allows "}}

data: {"type":"delta","payload":{"text":"600 requests per minute."}}

data: {"type":"done","payload":{"conversation_id":"conv_01HZYB","tokens":204,"latency_ms":688}}

Four event types: citations first (so a UI can show sources immediately), delta fragments, then done; a failure mid-stream arrives as error with a message. The stream is text/event-stream with cache-control: no-cache, no-transform. Each frame is a single data: line followed by a blank line — the SDKs parse exactly that.

Engine-native conversations

curl -sS -X POST "$AAC_API/indexes/documentation/assistant/native" \
  -H "Authorization: Bearer $AAC_KEY" \
  -H "Content-Type: application/json" \
	-d '{ "question": "Summarise the billing model", "conversation_id": "conv_engine_1" }'
{
	"index": "documentation",
	"answer": "Billing is metered by searches, documents, AI tokens and reindex runs…",
	"conversation_id": "conv_engine_1",
	"mode": "native",
	"request_id": "req_4f6a8c0e2b4d6f8a0c2e4b6d"
}

This path uses the engine's own conversation model (a configured model or the request's model_id) and keeps history on the engine side. It answers questions but returns no citation array — use /answer or /assistant when you need to show sources.

List the models the node offers (scope settings:write):

curl -sS "$AAC_API/indexes/documentation/conversation-models" \
  -H "Authorization: Bearer $AAC_KEY"

Choosing

  • Marketing FAQ, docs Q&A → /answer. Simple, stateless, citations for free.
  • Support chat that must remember the thread → /assistant with streaming. Persisted history, citations, extractive fallback.
  • You already run a conversation on the engine and only need a proxy → /assistant/native.
  • You want to control prompting entirely → call /search, take the hits, and build your own prompt. The platform's contract then stops at retrieval, which is often the right boundary.

On this page