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.
Make a search
Section titled “Make a search”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}Request fields
Section titled “Request fields”| 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. |
Choose what to search
Section titled “Choose what to search”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"].
Read the result
Section titled “Read the result”resultsis ordered by relevance. A document’s position is the rank of its best passage.scoreon a group is that best passage’s score. Compare scores inside one response only.passagesholds every matching passage from that document, in rank order.seqis the position of the passage in the document. It is stable.pageappears for PDF and PowerPoint.sectionappears when the document has headings. Neither is invented for a format that has none.referenceis the whole citation as one string that you can print:p. 4,Billing > Refunds, orexcerpt 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.
Page through the results
Section titled “Page through the results”limit and offset count passages, not documents. One page can therefore hold
fewer documents than passages, because several passages group under one
document.
-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.
Search and the chat tools
Section titled “Search and the chat tools”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.
Read a whole document after a search
Section titled “Read a whole document after a search”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.
When search is unavailable
Section titled “When search is unavailable”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.