Skip to article
NEXUSDocs
Documentation/Developers
Operator reference

Search with the API

Query the Nexus index programmatically and get ranked documents back

Before you begin

This reference describes the underlying platform. Use a Nexus school release with its school membership, class policy, and cost controls. Installing a base engine alone does not add those controls.

Nexus exposes two search endpoints, and which one you want depends on what you are building.

EndpointUse it when
POST /searchYou want Nexus's search results inside your own application: RAG pipelines, agents, integrations, anything that needs ranked passages rather than an answer. Runs the same retrieval pipeline as the Search action in chat.
POST /search/send-search-messageYou are building a search interface. This is the endpoint behind the Nexus Search UI, and it adds keyword expansion, LLM document selection, streaming, and per-user search history.

Neither endpoint generates an answer. To have a model read the results and write a response, use POST /chat/send-chat-message instead.

Both endpoints search only the documents the calling user is allowed to see, so the same query run by two users can return different results. Both also need a vector database: on deployments running with DISABLE_VECTOR_DB set (Nexus Lite), they answer with 501.

POST /search#

The request needs nothing but a query. Everything else narrows the search or changes how the query is interpreted.

ParameterDescription
queryThe query to search for. Between 1 and 2048 characters.
sourcesRestrict results to these connector source types, e.g. ["slack", "google_drive"].
document_setsRestrict results to documents in these document sets, by name.
tagsRestrict results to documents carrying all of these metadata tags, as a list of {"tag_key": ..., "tag_value": ...} objects.
time_cutoffISO 8601 timestamp. Only documents updated on or after this moment are returned. Timestamps without a timezone are treated as UTC.
persona_idSearch as an Agent. The Agent's document sets, attached documents and search start date apply on top of the other filters, and its LLM is used for query expansion.
provider / modelThe LLM used for query expansion and section selection. Both must be sent together, and the caller must have access to the provider. Defaults to the Agent's LLM, or the deployment default.
skip_query_expansionRun the query as written instead of rewriting and expanding it first. Useful when the query is already precise, or when you have your own expansion step.
message_historyPreceding conversation turns, so a query like "what about last quarter?" can be interpreted in context. Defaults to query on its own.

Results come back most relevant first:

{
"results": [
  {
    "citation_id": 1,
    "title": "Q3 Planning",
    "content": "Full text of the matched section...",
    "link": "https://...",
    "source_type": "google_drive",
    "updated_at": "2026-08-14T09:31:00Z"
  }
]
}

citation_id identifies the source document, not the result: several results share one citation_id when the search returned multiple non-overlapping sections of the same document.

import requests

API_BASE_URL = "https://school.narb.cc/api"  # or your own domain
API_KEY = "YOUR_KEY_HERE"

response = requests.post(
    f"{API_BASE_URL}/search",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "query": "What is our parental leave policy?",
        "sources": ["confluence", "google_drive"],
    },
)

for result in response.json()["results"]:
    print(f"[{result['citation_id']}] {result['title']} - {result['link']}")
#!/bin/bash

API_BASE_URL="https://school.narb.cc/api"  # or your own domain
API_KEY="YOUR_KEY_HERE"

curl -s -X POST "${API_BASE_URL}/search" \
  -H "Authorization: Bearer ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What is our parental leave policy?",
    "sources": ["confluence", "google_drive"]
  }' | jq '.results[] | {citation_id, title, link}'

POST /search/send-search-message#

This endpoint takes a different set of parameters, aimed at a search interface rather than a retrieval pipeline.

ParameterDescription
search_queryThe query to search for.
filtersRestrict which documents are searched: source_type, document_set, created_at_range / updated_at_range, and tags.
num_hitsMaximum number of merged sections to return. Defaults to 30.
hybrid_alphaBalance between vector and keyword matching, from 0.0 (pure keyword) to 1.0 (pure vector). Leave unset to use the deployment's HYBRID_ALPHA, which defaults to 0.5.
include_contentWhen true, each result carries the full text of the matched section in content. When false, content is null and only blurb is populated.
run_query_expansionHave an LLM generate extra keyword queries. Every query runs in parallel and the results are merged with weighted reciprocal-rank fusion, the original query counting twice as much as each expansion.
num_docs_fed_to_llm_selectionHand the top N sections to an LLM that picks the most relevant ones. Omit it to skip the extra LLM call.
streamWhether to stream packets as they are produced. Defaults to false, unlike the chat API.

Both LLM-backed options are optional and each costs an LLM call, so leave them off for a plain lexical/semantic search. Query expansion widens recall on short keyword queries; document selection narrows a long result list down to what actually answers the query, reporting its picks in llm_selected_doc_ids without dropping the other results.

Non-streaming response#

{
"all_executed_queries": ["parental leave policy"],
"search_docs": [
  {
    "document_id": "...",
    "semantic_identifier": "Parental Leave",
    "link": "https://...",
    "blurb": "...",
    "content": null,
    "source_type": "confluence",
    "score": 0.82
  }
],
"llm_selected_doc_ids": null,
"error": null
}

all_executed_queries holds more than one entry only when run_query_expansion was set. llm_selected_doc_ids is null when LLM selection was not requested or failed, and an empty list when it ran and chose nothing. If the search fails partway through, error is set and the other fields hold whatever was gathered before the failure.

Streaming response#

With stream: true the response is text/event-stream, one JSON object per line, in this order:

Packet typeContents
search_queriesall_executed_queries. the original query plus any expansions.
search_docssearch_docs. the ranked results.
llm_selected_docsllm_selected_doc_ids. Sent only when num_docs_fed_to_llm_selection was set.
search_errorerror. Sent in place of the remaining packets if the search fails.
import json

import requests

API_BASE_URL = "https://school.narb.cc/api"  # or your own domain
API_KEY = "YOUR_KEY_HERE"

with requests.post(
    f"{API_BASE_URL}/search/send-search-message",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    json={
        "search_query": "What is our parental leave policy?",
        "num_hits": 10,
        "include_content": True,
        "stream": True,
    },
    stream=True,
) as response:
    for line in response.iter_lines():
        if not line:
            continue
        packet = json.loads(line)
        if packet["type"] == "search_docs":
            for doc in packet["search_docs"]:
                print(doc["semantic_identifier"], doc["link"])
        elif packet["type"] == "search_error":
            print("Search failed:", packet["error"])
#!/bin/bash

API_BASE_URL="https://school.narb.cc/api"  # or your own domain
API_KEY="YOUR_KEY_HERE"

curl -s -N -X POST "${API_BASE_URL}/search/send-search-message" \
  -H "Authorization: Bearer ${API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "search_query": "What is our parental leave policy?",
    "num_hits": 10,
    "include_content": true,
    "stream": true
  }' | jq -c 'select(.type == "search_docs") | .search_docs[] | {semantic_identifier, link}'

Search history#

Every query sent through POST /search/send-search-message by a signed-in user is recorded, and GET /search/search-history reads back that user's own queries, most recent first. Pass limit (1-1000, default 100) and filter_days to narrow the window. Queries sent to POST /search and to the chat API are not recorded there.

Next Steps#

Guide: Send a Message to Nexus#

Have an Agent read the results and answer, instead of ranking documents

Guide: Use the Ingestion API#

Index your own documents so they show up in search

NEXUS

Nexus helps students think, practice, and learn, with teachers guiding AI use.

[ Support ]

[ NARB TECHNOLOGY INC. ]

Nexus is a school AI platform by narb Technology Inc. · 16192 Coastal Hwy, Lewes, DE 19958

© 2026 narb Technology Inc.

Nexus

Nexus helps schools make room for questions, practice, and reflection — with teacher guidance in view.

[ Contact us through e-mail ]

© 2026 narb Technology Inc.

NEXUS

Nexus helps students think, practice, and learn, with teachers guiding AI use.

[ Support ]

[ NARB TECHNOLOGY INC. ]

Nexus is a school AI platform by narb Technology Inc.

© 2026 narb Technology Inc.