Search your objects
Rank the objects or passages of a bucket against a text. One call matches exact words and meaning together.
Overview
Search ranks the objects of one bucket against a text. There is one search call. It matches the exact words and the meaning together, so you do not choose between a keyword index and a vector index, and you do not run two queries and merge them yourself.
target names what to search: a bucket id (bkt…) or a dataset id (dst…). This guide searches a bucket. To search a dataset that Graphon curates, read Query a dataset.
A result names an object and the spans inside it that matched. A span is chars of the text of an object, pages of a section or a figure of a PDF or an office file, seconds of a video or a recording, or the whole file, such as an image. It runs from start to end, without end itself. A text match in a PDF or an office file is a chars span with pages inside it: the pages that its chars cover. Pages count from 1, so page 1 is the first page. Chars count Unicode code points from 0, and seconds count from 0. A span holds no text: read it with Read spans. Read the whole object from the storage API with its key and versionId.
Before you start
The bucket must have indexing enabled. That setting is fixed when the bucket is created, so a bucket made without it never answers a search. Create one with --indexing enabled, as in Create your first bucket.
An object answers only after it finishes indexing. Check the bucket before your first search, and report the files that will never answer:
CLI
graphon buckets index-status support-tickets --watch
graphon buckets index-status support-tickets --filter failedA version in failed or unsupported is not searchable, and no retry changes that. While a new version indexes, the older version keeps answering.
Run a search
CLI
graphon search support-tickets "connection pool exhausted"
graphon search support-tickets "ERR_POOL_TIMEOUT" --mode keyword --limit 20
graphon search support-tickets "connection pool exhausted" --result-type passagecURL
curl --request POST \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/search" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"target": "'"$GRAPHON_BUCKET_ID"'",
"query": "checkout connection pool exhausted",
"mode": "hybrid",
"resultType": "object",
"limit": 10
}'A viewer key is enough. Graphon stores nothing from the call.
Choose the mode and the result type
| Field | Values | What it changes |
|---|---|---|
mode | hybrid, keyword | hybrid matches exact words and meaning together. keyword matches exact words only. hybrid is the default. |
resultType | object, passage | object returns one result for each file, with up to 3 spans. passage returns one result for each span, so a file can appear more than once. object is the default. |
limit | 1 to 100 | Results on one page. The default is 10. Keep it small and read the best results first. |
Use hybrid for most searches. Use keyword for exact words, such as an ID, a code, or an error message. Use passage when you feed the spans to a model, and object when a person reads a list of files.
Read the response
Response
{
"results": [
{
"key": "incidents/2026-10-03-checkout.pdf",
"versionId": "ver_3f9c2a7d1e4b4c8a9f0d6b2e5a1c7d3f",
"contentType": "application/pdf",
"rank": 1,
"score": 0.83,
"spans": [
{
"key": "incidents/2026-10-03-checkout.pdf",
"versionId": "ver_3f9c2a7d1e4b4c8a9f0d6b2e5a1c7d3f",
"unit": "chars",
"start": 18240,
"end": 19310,
"pages": { "start": 4, "end": 5 }
}
]
}
],
"nextCursor": "eyJ2IjoxLCJnIjoiZ2VuXzAxSkE3UTNWOEsifQ",
"execution": { "mode": "hybrid" },
"timingMs": { "retrieval": 34, "total": 47 },
"warnings": []
}score orders the results of this one request. It is not a confidence, and you cannot compare it between requests.
Read spans
Send up to 50 spans to Read spans exactly as search returned them. You get one result for each span, in the same order. A viewer key is enough. include names the parts of each result: text (the default) is the text that indexing extracted, raw is the span cut from the object in its own format, and object is the whole object version.
CLI
graphon read-spans support-tickets incidents/2026-10-03-checkout.pdf \
--version-id ver_3f9c2a7d1e4b4c8a9f0d6b2e5a1c7d3f --unit chars --start 18240 --end 19310cURL
curl --request POST \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/read-spans" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"target": "'"$GRAPHON_BUCKET_ID"'",
"spans": [
{
"key": "incidents/2026-10-03-checkout.pdf",
"versionId": "ver_3f9c2a7d1e4b4c8a9f0d6b2e5a1c7d3f",
"unit": "chars",
"start": 18240,
"end": 19310,
"pages": { "start": 4, "end": 5 }
}
],
"include": ["text"]
}'| The object | text | raw |
|---|---|---|
| A PDF or an office file | The text of the span. A pages span gives the text of its pages, as a chars span with pages. | A PDF of only the pages of the span. A chars span gives the pages that hold its chars. |
| A text file or a mail | The text of the span. | The text of the span. |
| A video or a recording | lines, each with start and end seconds and its words. text holds the words of the lines, one line each. | A clip of the span. |
| An image | Left out: an image has no text. | The image. |
raw and object are binary parts, with contentType, sizeBytes, and either data (base64) or url. With delivery inline (the default), parts come back as data, up to 20 MiB a call, in span order. A part past that comes back as a url. With delivery url, every part comes back as a url. A url stops working at its expiresAt: read the span again for a new one. With text, raw is cut to the same place as the text.
A large span comes back in parts. The result span is the part returned, in the unit of the content: the text of pages comes back as chars with pages. next is the body of the next read: the rest of each span that did not fit, with the same include and delivery. Send it with target to read on, until it is null. The times of a transcript line have a fraction. To read the clip of a line, send a seconds span from the floor of its start to the ceiling of its end.
A span that cannot be read has an error on its own result. The other spans still read.
| Error code | What it means |
|---|---|
| not_found | This bucket does not hold the object version, or it was deleted. |
| invalid_span | The unit does not fit the object, pages does not match the chars, or nothing of the object is inside the span. |
| content_unavailable | The content of the span is not stored or cannot be read. Read another span, or upload the object again. |
Filter by metadata
A filter keeps only the objects whose metadata matches. Search, Answer a question, and Start research all take the same filter. With keys or keyPrefix, an object must match all of them. Metadata also helps ranking with no filter: a search for a word in an object's metadata finds the object.
- List the metadata fields of the bucket, each with a type. A filter can name only a listed field. An editor key is needed.
- Upload objects with metadata:
x-amz-meta-team: searchfrom S3 tools,--metadata team=searchongraphon objects put, ormetadataon a presigned upload. - Search with a filter that names the fields.
CLI
graphon buckets metadata-fields set support-tickets --field team=text --field year=number
graphon objects put support-tickets incidents/pool.md --file pool.md --metadata team=search --metadata year=2026
graphon search support-tickets "connection pool" --filter '{"and":[{"field":"team","op":"eq","value":"search"},{"field":"year","op":"gte","value":2025}]}'cURL: list the fields
curl --request PATCH \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/buckets/$GRAPHON_BUCKET_ID/metadata-fields" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{ "fields": { "team": { "type": "text" }, "year": { "type": "number" } } }'cURL: search with a filter
curl --request POST \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/search" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"bucketId": "'"$GRAPHON_BUCKET_ID"'",
"query": "connection pool",
"filter": {
"and": [
{ "field": "team", "op": "eq", "value": "search" },
{ "field": "year", "op": "gte", "value": 2025 }
]
}
}'A clause is { field, op, value }. and, or, and not combine clauses, at most 4 levels deep and 32 clauses in all. The field type sets the operators and how a value reads:
| Type | Operators | A value that fits |
|---|---|---|
| text | eq, neq, gt, gte, lt, lte, prefix, in | Any text. |
| number | eq, neq, gt, gte, lt, lte, in | A number, or a decimal string such as "2026". |
| time | eq, neq, gt, gte, lt, lte | An RFC 3339 time, such as 2026-10-29T09:00:00Z. |
| bool | eq, neq, in | true or false, or the text "true" or "false". |
neq and not keep objects that have no value for the field. A value that does not fit the type never matches; Get metadata fields counts them in invalidValues.
List fields before you upload
indexing while the index is rewritten, which takes up to about 30 minutes. Meanwhile a filter on that field returns 409 metadata_field_indexing with a Retry-After header. Searches without a filter on it keep working.| Error | What it means |
|---|---|
| 400 invalid_filter | details.field names the field. details.reason is not_listed (the bucket does not list it), op_not_allowed (its type does not take the operator), or invalid_value (the value does not fit its type). |
| 409 metadata_field_indexing | The field is still indexing after a change. Wait for Retry-After, or search without it. |
Page through the results
A response with more results carries nextCursor. Send it back with the same body: a cursor that arrives with a changed query or mode is rejected. A cursor works for 40 minutes, and one search reaches at most 200 passages.
cURL
curl --request POST \
--url "https://api.beta.graphon.ai/v1/workspaces/$GRAPHON_WORKSPACE_ID/search" \
--header "Authorization: Bearer $GRAPHON_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"target": "'"$GRAPHON_BUCKET_ID"'",
"query": "checkout connection pool exhausted",
"cursor": "'"$NEXT_CURSOR"'"
}'When a search finds nothing
An empty result list with a warning is not an error. The call succeeded and the bucket had nothing to return.
| Warning | Meaning |
|---|---|
index_empty | The bucket has no indexed object yet. Upload files, or wait for indexing to finish. |
index_failed | Every object in the bucket failed to index. Read the errors with graphon buckets index-status --filter failed. |
A bucket that is still indexing
409 index_not_ready with a Retry-After header. Wait for the header's seconds and ask again. A bucket created without indexing returns 409 indexing_disabled, which no retry fixes.Next
- Get a written answer instead of a list in Answer a question.
- Read every field in the Search objects reference.
- Keep one tenant's results apart in Tenant isolation.