Search

Send a question, and get the passages that answer it, with the place of each one in its file.

POST /v1/search returns up to 10 passages, best first. Each passage comes with its text and its location in the file, so your product can show where it came from.

The smallest request

You need three fields: the question, the drives to search, and the effort.

curl https://byterover.ai/api/v1/search \
  -H "Authorization: Bearer $BYTEROVER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Which policy section covers EU refunds?",
    "drive_ids": ["drv_4h7k2m9q8r1t5v6w3x0y"],
    "effort": "medium"
  }'

Request fields

FieldTypeMeaning
querystring, requiredThe question, in plain language. Up to 2,048 characters.
drive_idslist of strings, requiredOne or more drives your key can reach. Several drives still give you a single ranked list.
effortmedium or high, requiredHow much work the search does. There's no default. See Search effort.
folderstringA folder path such as /policies. The search covers that folder and everything under it. / means the whole drive.
filtersobjectNarrows the search by tag, file type and date. See Filters.
group_by_documentbooleanReturns at most one passage per document. Defaults to true. See One passage for each file.

Filters

Every filter you set must match. Filters narrow where ByteRover looks, so a filtered search still returns the best passages within that narrower set of files.

FilterTypeMatches
tagslist of stringsDocuments that have every tag in the list, such as ["legal", "2026"].
file_typelistDocuments of any of these types: pdf, docx, pptx, xlsx, html, md, txt, eml, code.
updated_afterdate or timeDocuments that changed on or after this moment.
updated_beforedate or timeDocuments that changed on or before this moment.

Dates are ISO 8601. A date without a time, such as 2026-06-01, covers that whole day in UTC.

Signed legal contracts that changed in 2026
{
  "query": "Which contracts renew automatically, and what notice do they need?",
  "drive_ids": ["drv_4h7k2m9q8r1t5v6w3x0y"],
  "effort": "high",
  "folder": "/legal",
  "filters": {
    "tags": ["legal", "signed"],
    "file_type": ["pdf", "docx"],
    "updated_after": "2026-01-01"
  }
}

The response

Response
{
  "search_id": "srch_8f2k1m0q7r3t",
  "results": [
    {
      "passage_id": "psg_2c9d",
      "drive_id": "drv_4h7k2m9q8r1t5v6w3x0y",
      "document_id": "doc_7n4p1s8v2x5z9b3d6f0h",
      "revision": 3,
      "text": "Customers in the European Union may cancel an order within 14 days of delivery and receive a full refund. The refund goes back to the original payment method within 10 working days.",
      "score": 0.94,
      "start_byte": 18244,
      "end_byte": 18431,
      "location": {
        "page": 6,
        "heading_path": ["Refunds", "European Union"],
        "before": "...outside the European Union, the standard terms apply. ",
        "after": " Send any refund request from the European Union to the payments team..."
      }
    }
  ],
  "documents": [
    {
      "document_id": "doc_7n4p1s8v2x5z9b3d6f0h",
      "drive_id": "drv_4h7k2m9q8r1t5v6w3x0y",
      "name": "customer-refund-policy.pdf",
      "path": "/policies/customer-refund-policy.pdf",
      "folder": "/policies",
      "revision": 3
    }
  ],
  "effort": "medium",
  "search_ms": 812
}
FieldMeaning
search_idIdentifies this search. Log it, and include it when you contact support about a search that went wrong.
resultsUp to 10 passages, best first. The list can be empty; see When nothing answers.
results[].textThe passage text, exactly as it appears in the file.
results[].document_id, drive_idThe document and drive the passage comes from.
results[].revisionThe document revision that was searched.
results[].scoreThe value results are ranked by. Only compare scores within the same search.
results[].pickedOnly at high: true for passages chosen as direct support for the question. Picked passages come first.
results[].start_byte, end_byteWhere the passage sits in the document's text, as UTF-8 byte offsets. Use them to read around the passage.
results[].locationWhere a person can find the passage in the file. See Location.
documentsEach result's file, once, in the order the results first name it: document_id, drive_id, name, path, folder and its current revision. Join a result to its file by document_id. A file renamed or moved since it was indexed shows its current name and path; a file deleted since is left out, and its results still come back.
effortThe effort the search ran at.
search_msHow long the search took, in milliseconds.

Location

FieldWhen it is thereMeaning
pagePDFs and other paged filesThe page number, starting at 1.
slidePowerPointThe slide number, starting at 1.
sheetExcelThe sheet name.
heading_pathAlways, but it can be emptyThe headings above the passage, from the top level down.
before, afterAlwaysUp to 300 characters of the text just before and after the passage.

Show the source to your user

A result has most of what a citation needs: the text, the page and the heading path. Its file is in documents, under the same document_id, with its name and path, so a citation can read: customer-refund-policy.pdf, page 6, Refunds > European Union.

files = {doc["document_id"]: doc for doc in found["documents"]}
for result in found["results"]:
    file = files.get(result["document_id"])
    location = result["location"]
    place = f", page {location['page']}" if "page" in location else ""
    print(f"{file['name'] if file else 'A deleted file'}{place}, {' > '.join(location['heading_path'])}")

A file deleted after it was indexed has no entry in documents, though its results still come back.

One passage for each file

By default, each result comes from a different document, which gives you a wide view across your files.

When the answer is spread through one long document, such as a contract or a filing, set group_by_document to false so several passages can come from the same document.

When nothing answers

Search ranks passages; it doesn't decide whether they answer the question. If your files don't cover a question, you can still get passages that are close to the subject. You only get an empty list (with status 200) when nothing in the drives comes close.

The API returns passages only; a written answer with "not found" is in the app. To find out whether your files actually answer a question, pass the passages to your own model and instruct it to say the files don't answer the question when no passage does.

See Results.

If it fails

Errors raised by the search itself include a search_id. These are the most common:

StatusCodeWhat to do
400INVALID_REQUESTCheck the message. effort is required, and drive_ids needs at least one drive.
402BALANCE_EXHAUSTEDThe team's balance is empty. A team owner or admin needs to add money.
403FORBIDDENThe key can't reach one of the drives. drive_ids in the error lists which ones.
503SEARCH_UNAVAILABLE, DRIVE_UNAVAILABLEWait briefly, then retry with the same effort.
504TIMEOUTRetry with the same effort.

For the full list of codes, see Errors and retries.

On this page