---
title: Search collection
description: Retrieve ranked files or passages, with the spans that matched, from one collection.
doc_version: 0.1.0-preview
last_updated: 2026-09-14
---

# Search collection

`POST /namespaces/{namespace}/collections/{collection}:search`

Retrieve ranked files or passages, with the spans that matched, from one collection.

**Permission:** Cluster key, or a scoped key with search on this collection.

[API key types and authentication](/engine/reference/authentication.md).

Search returns spans and never generates an answer. A span holds no content: read it with Read spans. Use Answer for a cited answer.

result_type file (the default) returns one row per file, with up to three spans. passage returns one row per place, with one span, so a file can come back in several rows. Both rows have the same fields.

Pages count from 1: page 7 is start 7, end 8. Chars count Unicode code points from 0, and seconds count from 0. end is the first unit after the span.

hybrid is the default mode. It matches exact words and meaning together. keyword matches exact words only, such as IDs, codes, and error strings. hybrid needs the semantic processing flag. keyword needs the keyword flag.

Empty collections return 200 with no results. When keyword search is not available, a keyword search is 503 temporarily_unavailable, not retryable. Indexing with no ready files returns collection_not_ready. A failed collection returns no results and a warning.

Send principals only when access_control is enforced. No key bypasses the access rules.

Use next_cursor with the same request and key. Search cursors expire after 40 minutes.

## Example request

```http
POST /namespaces/acme/collections/incident-logs:search HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>
Content-Type: application/json

{
  "query": "database timeout",
  "mode": "hybrid",
  "limit": 10
}
```

## Path parameters

### `namespace`

**string · required**

Namespace name or ns_ ID. It must be visible to this key.

### `collection`

**string · required**

Collection name or col_ ID within the namespace. It must be visible to this key.

## Request body

### `cursor`

**string · optional · nullable**

Opaque continuation token from next_cursor. Keep the same key, filters, and other request values. Omit it for the first page.

### `filter`

**FilterClause · optional · nullable**

Structured file selection using built-in fields or the listed metadata fields. Combine clauses with and, or, and not. A metadata key that the collection does not list returns invalid_filter. Filters select data and do not grant or restrict key access.

- Maximum depth: 8
- Maximum clauses: 100
- An in list holds 1–100 values.

#### Comparison

Compare one built-in or metadata field.

##### `filter.field`

**string · required**

path, content_type, bytes, created_at, updated_at, status, or metadata.<key>. The key must be in the collection’s metadata fields. Otherwise the request returns invalid_filter.

##### `filter.op`

**string · required**

Comparison operator. path and content_type support eq, neq, prefix, and in. bytes supports eq, neq, gt, gte, lt, lte, and in. Timestamps support eq, neq, gt, gte, lt, and lte. status supports eq, neq, and in. A metadata field supports the operators of its type. text supports all of them. number supports eq, neq, gt, gte, lt, lte, and in. time supports eq, neq, gt, gte, lt, and lte. bool supports eq, neq, and in.

- `eq`: Equal.
- `neq`: Not equal.
- `gt`: Greater than.
- `gte`: Greater than or equal.
- `lt`: Less than.
- `lte`: Less than or equal.
- `prefix`: String starts with this value.
- `in`: Equal to one value in a nonempty array.

##### `filter.value`

**JSON scalar | array<JSON scalar> · required**

Value of the correct type for the field. bytes uses non-negative integers. Timestamps need an ISO 8601 timezone. in takes an array. A metadata value is read by the field type: a number field accepts 10 or "10", a time field needs an RFC 3339 time, and a bool field accepts true, false, "true", or "false". null matches a stored null with eq, neq, and in, where the field type allows the operator.

#### All clauses

##### `filter.and`

**array<FilterClause> · required**

Nonempty list of clauses that must all match. Each entry uses this same FilterClause grammar.

#### Any clause

##### `filter.or`

**array<FilterClause> · required**

Nonempty list of clauses where at least one must match. Each entry uses this same FilterClause grammar.

#### Negated clause

##### `filter.not`

**FilterClause · required**

One clause that must not match. It uses this same FilterClause grammar.

### `interpretation`

**string · optional**

literal uses the supplied search text. natural_language can derive search text and filters from a sentence. It does not generate an answer.

Default: `"literal"`.

- `literal`: Use the supplied search text.
- `natural_language`: Interpret a sentence as search text and filters.

### `limit`

**integer · optional**

Maximum results in this page. Default 10, minimum 1, maximum 100.

Default: `10`.

- Minimum: 1
- Maximum: 100

### `mode`

**string · optional**

hybrid (the default) matches exact words and meaning together. keyword matches exact words only, such as IDs, codes, and error strings. hybrid needs the semantic processing flag. keyword needs the keyword flag.

Default: `"hybrid"`.

- `keyword`: Exact words only, such as IDs, codes, and error strings.
- `hybrid`: Exact words and meaning matched together. The default.

### `principals`

**array<string> · optional · nullable**

End-user and group identifiers supplied by your server. Required on enforced collections and invalid when access control is off. An empty list gives no results. Maximum 4,096 entries, each at most 256 UTF-8 bytes.

### `query`

**string · required**

Search text. Must contain a non-space character. Maximum 32,000 characters.

- Minimum characters: 1
- Maximum characters: 32000

### `result_type`

**string · optional**

file returns one row per file with up to three spans. passage returns one row per matching place, with one span. Both rows have the same shape.

Default: `"file"`.

- `file`: Group matching passages by source file.
- `passage`: Return individual matching passages.

## Responses

### 200 · Success

Ranked files with their spans. Timing and score values are illustrative.

Content type: `application/json`.

#### Response headers

##### `x-engine-request-id`

**string · required**

The Engine request ID. Retain it when investigating a failed or unexpected request.

##### `x-request-id`

**string · required**

The X-Request-Id you sent, echoed when it is a valid ID. Otherwise, the Engine request ID.

```json
{
  "request_id": "req_example",
  "index_generation": "gen_example",
  "execution": {
    "mode": "hybrid",
    "interpretation": "literal"
  },
  "results": [
    {
      "file_id": "fil_01K6Q6N7D8E9F0G1H2J3K4M5N6",
      "path": "/reports/incident.txt",
      "content_type": "text/plain",
      "rank": 1,
      "score": 0.81,
      "spans": [
        {
          "file_id": "fil_01K6Q6N7D8E9F0G1H2J3K4M5N6",
          "unit": "chars",
          "start": 0,
          "end": 77
        }
      ]
    }
  ],
  "timing_ms": {
    "retrieval": 34,
    "total": 47
  },
  "warnings": [],
  "next_cursor": null
}
```

#### Response fields

##### `execution`

**object · required**

The mode and interpretation of this request.

###### `execution.interpretation`

**string · required**

Interpretation used for the search text.

- `literal`: Use the supplied search text.
- `natural_language`: Interpret a sentence as search text and filters.

###### `execution.mode`

**string · required**

Retrieval mode used for this search.

- `keyword`: Exact words only, such as IDs, codes, and error strings.
- `hybrid`: Exact words and meaning matched together. The default.

##### `index_generation`

**string · required · nullable**

Opaque identifier for the searchable generation. It changes when indexed data changes. Null means that no searchable generation exists.

##### `next_cursor`

**string · required · nullable**

Token for the next page, or null when this result has no next page. Do not parse or modify the token.

##### `request_id`

**string · required**

The Engine request ID, also returned in the x-engine-request-id response header.

##### `results`

**array<object> · required**

One row per file with result_type file, or one row per place with result_type passage. An empty array means there are no matching results.

###### `results[].content_type`

**string · required**

MIME type of the file, such as text/plain, application/pdf, or video/mp4.

###### `results[].file`

**object · optional · nullable**

Not returned yet. The whole stored file.

###### `results[].file.content_type`

**string · required**

Content type of the part.

###### `results[].file.data`

**string · optional · nullable**

The bytes, as base64. Present when the part is inline.

###### `results[].file.expires_at`

**string (date-time) · optional · nullable**

ISO 8601 UTC time after which the URL expires.

###### `results[].file.page`

**integer · optional · nullable**

Not returned yet. The page of a page screenshot, from 1.

###### `results[].file.seconds`

**number · optional · nullable**

Not returned yet. The time of a video frame, in seconds from the start.

###### `results[].file.size_bytes`

**integer · required**

Size of the part in bytes, before base64.

###### `results[].file.url`

**string · optional · nullable**

Signed URL to the bytes. It works for 15 minutes. On a client deployment it is a gs:// URI. Present when the part did not fit inline, or delivery is url.

###### `results[].file_id`

**string · required**

ID of the file that contains this result.

###### `results[].matches`

**array<object> · optional · nullable**

Not returned yet. One match for each span, in the same order.

###### `results[].matches[].highlight`

**object · optional · nullable**

The text around the match, and where the matched words are.

###### `results[].matches[].highlight.match_ranges`

**array<[integer, integer]> · required**

The [start, end) code point ranges of the matched words in text.

###### `results[].matches[].highlight.text`

**string · required**

About 300 characters around the match.

###### `results[].matches[].raw`

**object · optional · nullable**

The match cut from the file in its own format.

###### `results[].matches[].raw.content_type`

**string · required**

Content type of the part.

###### `results[].matches[].raw.data`

**string · optional · nullable**

The bytes, as base64. Present when the part is inline.

###### `results[].matches[].raw.expires_at`

**string (date-time) · optional · nullable**

ISO 8601 UTC time after which the URL expires.

###### `results[].matches[].raw.page`

**integer · optional · nullable**

Not returned yet. The page of a page screenshot, from 1.

###### `results[].matches[].raw.seconds`

**number · optional · nullable**

Not returned yet. The time of a video frame, in seconds from the start.

###### `results[].matches[].raw.size_bytes`

**integer · required**

Size of the part in bytes, before base64.

###### `results[].matches[].raw.url`

**string · optional · nullable**

Signed URL to the bytes. It works for 15 minutes. On a client deployment it is a gs:// URI. Present when the part did not fit inline, or delivery is url.

###### `results[].matches[].screenshots`

**array<object> · optional · nullable**

Images of the match.

###### `results[].matches[].screenshots[].content_type`

**string · required**

Content type of the part.

###### `results[].matches[].screenshots[].data`

**string · optional · nullable**

The bytes, as base64. Present when the part is inline.

###### `results[].matches[].screenshots[].expires_at`

**string (date-time) · optional · nullable**

ISO 8601 UTC time after which the URL expires.

###### `results[].matches[].screenshots[].page`

**integer · optional · nullable**

Not returned yet. The page of a page screenshot, from 1.

###### `results[].matches[].screenshots[].seconds`

**number · optional · nullable**

Not returned yet. The time of a video frame, in seconds from the start.

###### `results[].matches[].screenshots[].size_bytes`

**integer · required**

Size of the part in bytes, before base64.

###### `results[].matches[].screenshots[].url`

**string · optional · nullable**

Signed URL to the bytes. It works for 15 minutes. On a client deployment it is a gs:// URI. Present when the part did not fit inline, or delivery is url.

###### `results[].matches[].span`

**object · required**

The place of the match, inside the span of its result.

###### `results[].matches[].span.bbox`

**object · optional · nullable**

Not returned yet. The box of a figure on one page. Read spans accepts it and reads the whole page.

###### `results[].matches[].span.bbox.bottom`

**number · required**

The bottom edge, as a fraction of the page height from the top.

- Minimum: 0
- Maximum: 1

###### `results[].matches[].span.bbox.left`

**number · required**

The left edge, as a fraction of the page width from the left.

- Minimum: 0
- Maximum: 1

###### `results[].matches[].span.bbox.right`

**number · required**

The right edge, as a fraction of the page width from the left.

- Minimum: 0
- Maximum: 1

###### `results[].matches[].span.bbox.top`

**number · required**

The top edge, as a fraction of the page height from the top.

- Minimum: 0
- Maximum: 1

###### `results[].matches[].span.end`

**integer · optional · nullable**

First unit after the span. Absent when unit is file.

- Minimum: 0
- Maximum: 2147483647

###### `results[].matches[].span.file_id`

**string · required**

ID of the file within the collection.

###### `results[].matches[].span.pages`

**object · optional · nullable**

The pages that a chars span of a PDF or Office file covers, from 1, so a client can show a page number. Absent on every other span. Read spans refuses pages that do not match the chars.

###### `results[].matches[].span.pages.end`

**integer · required**

First page after the range.

- Minimum: 1

###### `results[].matches[].span.pages.start`

**integer · required**

First page of the range, from 1.

- Minimum: 1

###### `results[].matches[].span.start`

**integer · optional · nullable**

First unit of the span. Pages count from 1. Chars and seconds count from 0. Absent when unit is file.

- Minimum: 0
- Maximum: 2147483647

###### `results[].matches[].span.unit`

**string · required**

pages (a section or a figure of a PDF or Office file), chars (Unicode code points of the text form), seconds (whole seconds), or file (the whole file).

- `pages`: Pages of a PDF or an office file, from 1. Page 7 is start 7, end 8.
- `chars`: Unicode code points of the text form of a file, from 0: a txt, md, or mail file, or a PDF or office file.
- `seconds`: Whole seconds of a video or an audio file, from 0.
- `file`: The whole file, with no start or end.

###### `results[].matches[].text`

**string · optional · nullable**

The text of the match.

###### `results[].path`

**string · required**

Logical path of the matching file within the collection.

###### `results[].rank`

**integer · required**

One-based relevance rank across every page.

###### `results[].score`

**number · required**

Ranking value for this result. It is not a confidence percentage.

###### `results[].spans`

**array<object> · required**

Where the file matched. With result_type file, at most three places, best first, with no repeat. With result_type passage, one place. Send a span to Read spans to get its content.

###### `results[].spans[].bbox`

**object · optional · nullable**

Not returned yet. The box of a figure on one page. Read spans accepts it and reads the whole page.

###### `results[].spans[].bbox.bottom`

**number · required**

The bottom edge, as a fraction of the page height from the top.

- Minimum: 0
- Maximum: 1

###### `results[].spans[].bbox.left`

**number · required**

The left edge, as a fraction of the page width from the left.

- Minimum: 0
- Maximum: 1

###### `results[].spans[].bbox.right`

**number · required**

The right edge, as a fraction of the page width from the left.

- Minimum: 0
- Maximum: 1

###### `results[].spans[].bbox.top`

**number · required**

The top edge, as a fraction of the page height from the top.

- Minimum: 0
- Maximum: 1

###### `results[].spans[].end`

**integer · optional · nullable**

First unit after the span. Absent when unit is file.

- Minimum: 0
- Maximum: 2147483647

###### `results[].spans[].file_id`

**string · required**

ID of the file within the collection.

###### `results[].spans[].pages`

**object · optional · nullable**

The pages that a chars span of a PDF or Office file covers, from 1, so a client can show a page number. Absent on every other span. Read spans refuses pages that do not match the chars.

###### `results[].spans[].pages.end`

**integer · required**

First page after the range.

- Minimum: 1

###### `results[].spans[].pages.start`

**integer · required**

First page of the range, from 1.

- Minimum: 1

###### `results[].spans[].start`

**integer · optional · nullable**

First unit of the span. Pages count from 1. Chars and seconds count from 0. Absent when unit is file.

- Minimum: 0
- Maximum: 2147483647

###### `results[].spans[].unit`

**string · required**

pages (a section or a figure of a PDF or Office file), chars (Unicode code points of the text form), seconds (whole seconds), or file (the whole file).

- `pages`: Pages of a PDF or an office file, from 1. Page 7 is start 7, end 8.
- `chars`: Unicode code points of the text form of a file, from 0: a txt, md, or mail file, or a PDF or office file.
- `seconds`: Whole seconds of a video or an audio file, from 0.
- `file`: The whole file, with no start or end.

##### `timing_ms`

**object · required**

Measured retrieval and total durations in milliseconds.

###### `timing_ms.retrieval`

**integer · required**

Time spent retrieving matches, in milliseconds.

###### `timing_ms.total`

**integer · required**

Total time for the search operation, in milliseconds.

##### `warnings`

**array<object> · required**

Nonfatal conditions that help explain the result, such as every file failing to index.

###### `warnings[].code`

**string · required**

Machine-readable explanation of a nonfatal retrieval condition.

###### `warnings[].message`

**string · required**

Human-readable description of the condition.

## Errors

[Error format and retry guidance](/engine/reference/errors.md).

### 400 · `invalid_request`

A field, path, parameter, or conditional request rule is invalid.

Correct the request. Unknown fields are rejected.

```json
{
  "error": {
    "code": "invalid_request",
    "message": "A field, path, parameter, or conditional request rule is invalid.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 400 · `invalid_filter`

A filter uses an unsupported field, operator, value, or nesting structure, or a metadata key that the collection does not list. details.field names the field. details.reason is not_listed, op_not_allowed, or invalid_value for a metadata field.

Use the documented filter grammar and field-specific value types. List a metadata key with Update metadata fields before you filter on it.

```json
{
  "error": {
    "code": "invalid_filter",
    "message": "A filter uses an unsupported field, operator, value, or nesting structure, or a metadata key that the collection does not list. details.field names the field. details.reason is not_listed, op_not_allowed, or invalid_value for a metadata field.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 400 · `invalid_cursor`

The cursor expired or does not belong to this request and key.

Restart from the first page. Preserve the same key, filters, and request values when continuing.

```json
{
  "error": {
    "code": "invalid_cursor",
    "message": "The cursor expired or does not belong to this request and key.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 401 · `unauthenticated`

The API key is missing, invalid, or revoked.

Send an active key in Authorization: Bearer or X-API-Key.

```json
{
  "error": {
    "code": "unauthenticated",
    "message": "The API key is missing, invalid, or revoked.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 403 · `forbidden`

The key can see the resource but lacks permission for this operation.

Use a key with the required action or scope kind.

```json
{
  "error": {
    "code": "forbidden",
    "message": "The key can see the resource but lacks permission for this operation.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 404 · `not_found`

The resource is absent, deleted, or outside this key’s scope.

Check the identifier and the key’s namespace and collection scope.

```json
{
  "error": {
    "code": "not_found",
    "message": "The resource is absent, deleted, or outside this key’s scope.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 409 · `collection_not_ready`

The collection cannot serve this retrieval request yet.

Read retryable and details.retry_after_seconds. If processing failed, inspect file errors and correct the source.

```json
{
  "error": {
    "code": "collection_not_ready",
    "message": "The collection cannot serve this retrieval request yet.",
    "request_id": "req_example",
    "retryable": true,
    "details": {
      "retry_after_seconds": 30
    }
  }
}
```

### 413 · `request_too_large`

The JSON body exceeds the route limit: 6,291,456 bytes on :answer and research, 4,194,304 bytes on :search and :read-spans, and 1,048,576 bytes elsewhere.

Reduce the JSON body. Use upload or a URI source for larger file content.

```json
{
  "error": {
    "code": "request_too_large",
    "message": "The JSON body exceeds the route limit: 6,291,456 bytes on :answer and research, 4,194,304 bytes on :search and :read-spans, and 1,048,576 bytes elsewhere.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

### 503 · `temporarily_unavailable`

A dependency, cluster readiness condition, or capacity limit prevents this operation.

Inspect retryable and details. Retry only when permitted, preserving the idempotency key for the same write.

```json
{
  "error": {
    "code": "temporarily_unavailable",
    "message": "A dependency, cluster readiness condition, or capacity limit prevents this operation.",
    "request_id": "req_example",
    "retryable": true,
    "details": {}
  }
}
```

### 500 · `internal_error`

An unexpected server failure prevented this request from completing.

Retain request_id for investigation. Follow retryable before retrying a write.

```json
{
  "error": {
    "code": "internal_error",
    "message": "An unexpected server failure prevented this request from completing.",
    "request_id": "req_example",
    "retryable": false,
    "details": {}
  }
}
```

## Other request languages

### cURL

```bash
curl -sS -X POST "$GRAPHON_ENGINE_URL/namespaces/acme/collections/incident-logs:search" \
  -H "Authorization: Bearer $GRAPHON_ENGINE_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "query": "database timeout",
  "mode": "hybrid",
  "limit": 10
}'
```

### Python

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    os.environ["GRAPHON_ENGINE_URL"] + "/namespaces/acme/collections/incident-logs:search",
    data=json.dumps({"query": "database timeout", "mode": "hybrid", "limit": 10}).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + os.environ["GRAPHON_ENGINE_API_KEY"],
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(request) as response:
    print(response.read().decode())
```

### JavaScript

```javascript
const response = await fetch(process.env.GRAPHON_ENGINE_URL + "/namespaces/acme/collections/incident-logs:search", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GRAPHON_ENGINE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"query":"database timeout","mode":"hybrid","limit":10}),
});
console.log(response.status, await response.text());
```

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
