Skip to content

Search documents

POST /search returns the passages that match a query. The passages come back grouped under the document they belong to, in relevance order, with a citation for each one.

This is the retrieval layer with nothing on top. No model reads the result, and no answer is written. Your code decides what to do with the passages: put them in your own prompt, render them as a result list, or score them again yourself.

Terminal window
curl https://chat.app.urai.dev/api/v1/knowledge/search \
-H "Authorization: Bearer $URAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "refund window for annual plans",
"collections": ["billing-policies"],
"limit": 10
}'
{
"query": "refund window for annual plans",
"results": [
{
"document": {
"id": "3f2a7c19-...",
"name": "billing-policy-2026.pdf",
"collection_id": "9b1c8d2e-...",
"collection": "Billing policies"
},
"score": 0.82,
"passages": [
{
"seq": 41,
"page": 4,
"section": "Billing > Refunds",
"reference": "p. 4",
"score": 0.82,
"content": "Annual plans may be refunded in full within 30 days of renewal..."
},
{
"seq": 42,
"page": 4,
"reference": "p. 4",
"score": 0.64,
"content": "A refund is returned to the payment method on record..."
}
]
}
],
"limit": 10,
"offset": 0,
"has_more": true
}
Field Required Effect
query yes The question or the keywords. An empty query fails with missing_query.
collections no Collections to search. Omit it to search every collection in the organization.
document_ids no Narrow the search to these documents.
tags no Keep only the documents that carry one of these tags.
limit no Passages to return. The default is 8 and the highest is 100.
offset no Passages to skip.

A name in collections matches the slug or the display name, and case does not matter. billing-policies and Billing policies reach the same collection. A UUID does not work here, unlike the {reference} in a path.

An unknown name fails the whole request with 400 and the code unknown_collection. The message lists the slugs that are valid. Urai does not drop the name and search everything instead, because that would return passages from the documents you excluded.

document_ids follows the same rule. Every id must belong to a collection you are searching, or the request fails with document_not_found.

Tags match on any. A document with the tag 2026 or the tag policy matches "tags": ["policy", "2026"].

  • results is ordered by relevance. A document’s position is the rank of its best passage.
  • score on a group is that best passage’s score. Compare scores inside one response only.
  • passages holds every matching passage from that document, in rank order.
  • seq is the position of the passage in the document. It is stable.
  • page appears for PDF and PowerPoint. section appears when the document has headings. Neither is invented for a format that has none.
  • reference is the whole citation as one string that you can print: p. 4, Billing > Refunds, or excerpt 13.

A search that matches nothing returns an empty results array and has_more set to false. This is also what an organization with no indexed document gets.

limit and offset count passages, not documents. One page can therefore hold fewer documents than passages, because several passages group under one document.

Terminal window
-d '{ "query": "refund window", "limit": 20, "offset": 20 }'

There is no total. The index cannot report the number of matches honestly, so Urai answers has_more instead. It is an observation: Urai asks for one passage more than you wanted and tells you whether it was there.

Stop paging when has_more is false.

The same index serves four callers, and they differ in what they are for.

Caller Use it when
POST /search Your code writes the prompt, or renders the passages itself. Up to 100 passages, with paging.
The search_documents tool You want a finished answer from one request. Urai searches, and the model writes the reply. Up to 25 passages.
The Knowledge page in the app A person is looking something up.
urai:knowledge in uraijs Your tool or workflow code needs the whole text of a document, not the passages that matched.

The API allows a wider window than the tool, because a model’s context is the limit for the tool and your own reranking is not.

See Search knowledge collections for the tool.

Search returns passages. When your code needs the rest of the document, take the document.id from a result and open it with the urai:knowledge library inside a uraijs tool or workflow:

import { knowledge } from "urai:knowledge";
const doc = await knowledge.open(result.document.id);
for await (const line of doc.lines()) {
// the whole document, one line at a time, at any size
}

Use the passages to answer a question, and the library to process a file. See Read documents from code.

A search that Urai cannot run returns 502. It never returns an empty result for a failure, because “no passages” and “the search did not run” call for different behaviour in your application.