Graphon Docs

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 failed

A version in failed or unsupported is not searchable, and no retry changes that. While a new version indexes, the older version keeps answering.

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 passage

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",
    "mode": "hybrid",
    "resultType": "object",
    "limit": 10
  }'

A viewer key is enough. Graphon stores nothing from the call.

Choose the mode and the result type

FieldValuesWhat it changes
modehybrid, keywordhybrid matches exact words and meaning together. keyword matches exact words only. hybrid is the default.
resultTypeobject, passageobject 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.
limit1 to 100Results 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 19310

cURL

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 objecttextraw
A PDF or an office fileThe 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 mailThe text of the span.The text of the span.
A video or a recordinglines, 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 imageLeft 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 codeWhat it means
not_foundThis bucket does not hold the object version, or it was deleted.
invalid_spanThe unit does not fit the object, pages does not match the chars, or nothing of the object is inside the span.
content_unavailableThe 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.

  1. List the metadata fields of the bucket, each with a type. A filter can name only a listed field. An editor key is needed.
  2. Upload objects with metadata: x-amz-meta-team: search from S3 tools, --metadata team=search on graphon objects put, or metadata on a presigned upload.
  3. 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:

TypeOperatorsA value that fits
texteq, neq, gt, gte, lt, lte, prefix, inAny text.
numbereq, neq, gt, gte, lt, lte, inA number, or a decimal string such as "2026".
timeeq, neq, gt, gte, lt, lteAn RFC 3339 time, such as 2026-10-29T09:00:00Z.
booleq, neq, intrue 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

A field listed before the upload is indexed with each object at once. A field added to a bucket with objects is 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.
ErrorWhat it means
400 invalid_filterdetails.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_indexingThe 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.

WarningMeaning
index_emptyThe bucket has no indexed object yet. Upload files, or wait for indexing to finish.
index_failedEvery object in the bucket failed to index. Read the errors with graphon buckets index-status --filter failed.

A bucket that is still indexing

While no object is ready, the call returns 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

  1. Get a written answer instead of a list in Answer a question.
  2. Read every field in the Search objects reference.
  3. Keep one tenant's results apart in Tenant isolation.
Was this page helpful?
View as Markdown