Graphon Engine

Research

AI Draft

Start an answer that runs on its own, follow its progress, and read or cancel it.

View as Markdown

Start an answer that runs on its own, and read it when it ends. A research run takes the same request as Answer, but no connection stays open while Engine works.

When to use research

Answer holds the HTTP connection open until the answer is ready. If the client disconnects, Engine cancels the query. Research returns 202 at once with a run ID. Engine then works on the answer by itself and reports each change.

OperationUse it when

Answer

A person waits for the reply, and the question fits in one request. standard mode allows 120 seconds.

Research

The question can take minutes, as in ultra or recall mode, which allow 600 seconds. Also use it when your caller cannot hold a connection open, or when you want to show progress.

Both operations take the same body, run the same checks, and have the same time limits. An image in query_image adds 45 seconds. Both need the key’s query action.

Before you begin

Use a collection with ready files and a key with query access. Check query.modes in /info for the modes that your cluster supports. recall, web_search: "use_if_needed", and query_image need a cluster key.

To follow runs with events, ask the cluster operator to add research.updated and research.progress to a webhook. See Handle jobs and events. Without a webhook, read the run with GET.

Start a run

POST https://engine.example.com/namespaces/acme/collections/incident-logs/research

POST /namespaces/acme/collections/incident-logs/research HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>
Content-Type: application/json
Idempotency-Key: checkout-pool-research-1

{
  "query": "Find every incident in which checkout ran out of database connections.",
  "mode": "ultra",
  "return_sources": true
}

202 Accepted

{
  "id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "namespace": "acme",
  "collection": "incident-logs",
  "status": "queued",
  "mode": "ultra",
  "progress": null,
  "answer": null,
  "sources": null,
  "image_description": null,
  "usage": null,
  "error": null,
  "created_at": "2026-10-06T12:02:00Z",
  "updated_at": "2026-10-06T12:02:00Z",
  "completed_at": null
}

Save id. It starts with rsr_. You need it to read or cancel the run. The run starts as queued. A field that the run has not set yet is null.

Idempotency-Key is required. A repeat with the same key and body returns the same run in its current state. A different body with the same key returns 409 idempotency_conflict. Engine keeps the key for 24 hours. Use a new key for each new run.

Engine checks the request and the collection before it creates the run. A failed check returns its error at once, and Engine creates no run. See Handle errors.

Follow the run

StatusMeaning

queued

Engine accepted the run. The run has not started.

running

Engine is working on the answer. progress holds the last stage.

succeeded

The answer is ready.

failed

The run ended with an error. Read error.

canceled

The run stopped after a cancel.

succeeded, failed, and canceled are final. A run in a final status does not change again.

Engine sends research.updated when it creates the run and at each status change. The first event has data.previous_status: null. Engine sends research.progress each time progress changes. Each research event names the run in research_id. Its job_id and file_id are null.

Example webhook body

{
  "events": [
    {
      "id": "evt_01J8Z3K4N5P6Q7R8S9T0V1W2X4",
      "type": "research.progress",
      "occurred_at": "2026-10-06T12:04:00Z",
      "cluster": "graphon-engine",
      "namespace": "acme",
      "collection": "incident-logs",
      "job_id": null,
      "file_id": null,
      "research_id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
      "data": {
        "stage": "synthesizing",
        "label": "Synthesizing answer",
        "step": 3,
        "total_steps": 3
      }
    },
    {
      "id": "evt_01J8Z3K4N5P6Q7R8S9T0V1W2X5",
      "type": "research.updated",
      "occurred_at": "2026-10-06T12:05:00Z",
      "cluster": "graphon-engine",
      "namespace": "acme",
      "collection": "incident-logs",
      "job_id": null,
      "file_id": null,
      "research_id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
      "data": {
        "status": "succeeded",
        "previous_status": "running",
        "mode": "ultra"
      }
    }
  ]
}

A research.progress event has stage, label, step, and total_steps in data. Show label to people, and use stage in code. A research.updated event has status, previous_status, and mode, and error_code when the status is failed. Deduplicate each event by its id.

Each webhook has its own delivery order. A separate webhook for research events keeps them from waiting behind file events.

Read the run

Read the run at any time with GET. Use it when you have no webhook, after a missed event, or to load the answer after research.updated reports succeeded.

GET https://engine.example.com/namespaces/acme/collections/incident-logs/research/rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3

GET /namespaces/acme/collections/incident-logs/research/rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3 HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>

200 OK · running

{
  "id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "namespace": "acme",
  "collection": "incident-logs",
  "status": "running",
  "mode": "ultra",
  "progress": {
    "stage": "exploring",
    "label": "Exploring your files",
    "step": 2,
    "total_steps": 3
  },
  "answer": null,
  "sources": null,
  "image_description": null,
  "usage": null,
  "error": null,
  "created_at": "2026-10-06T12:02:00Z",
  "updated_at": "2026-10-06T12:03:00Z",
  "completed_at": null
}

If you poll, wait a few seconds between reads. Engine checks the stage of a running run every 5 seconds, so faster reads return the same progress.

Only the key that started the run, or a cluster key, can read or cancel it. Another key gets 404 not_found, as for a run that does not exist. The key must also reach the collection with the query action.

Read the answer and sources

200 OK · succeeded

{
  "id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "namespace": "acme",
  "collection": "incident-logs",
  "status": "succeeded",
  "mode": "ultra",
  "progress": {
    "stage": "synthesizing",
    "label": "Synthesizing answer",
    "step": 3,
    "total_steps": 3
  },
  "answer": "One incident matches: checkout exhausted its connection pool [[SRC:0001]].",
  "sources": {
    "[SRC:0001]": {
      "file_id": "fil_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
      "path": "/incidents/checkout.txt",
      "score": 0.81,
      "is_cited": true,
      "evidence": {
        "kind": "text",
        "text": "Checkout stalled because the service exhausted its connection pool.",
        "location": {
          "char_start": 0,
          "char_end": 67
        }
      }
    }
  },
  "image_description": null,
  "usage": {
    "credits": 4
  },
  "error": null,
  "created_at": "2026-10-06T12:02:00Z",
  "updated_at": "2026-10-06T12:05:00Z",
  "completed_at": "2026-10-06T12:05:00Z"
}

When the status is succeeded, the run holds answer, sources, image_description, and usage. These fields have the same shape as in an Answer response. usage.credits is the final credit count for the run. completed_at is the time the run ended.

Render citations as for Answer. [[SRC:0001]] in answer maps to the [SRC:0001] key in sources. See Render citations. sources is null when the request set return_sources: false.

The sources include only files that are still in the collection when the run ends. Engine removes a source whose file is gone, together with its markers.

Cancel a run

POST https://engine.example.com/namespaces/acme/collections/incident-logs/research/rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3:cancel

POST /namespaces/acme/collections/incident-logs/research/rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3:cancel HTTP/1.1
Host: engine.example.com
Authorization: Bearer <GRAPHON_ENGINE_API_KEY>

202 Accepted · cancel requested

{
  "id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "namespace": "acme",
  "collection": "incident-logs",
  "status": "running",
  "mode": "ultra",
  "progress": {
    "stage": "exploring",
    "label": "Exploring your files",
    "step": 2,
    "total_steps": 3
  },
  "answer": null,
  "sources": null,
  "image_description": null,
  "usage": null,
  "error": null,
  "created_at": "2026-10-06T12:02:00Z",
  "updated_at": "2026-10-06T12:03:00Z",
  "completed_at": null
}

A queued run becomes canceled at once. A running run can still show running in the response. It becomes canceled when Engine has stopped its work. Engine does not bill a canceled run.

Wait for research.updated with data.status: "canceled", or read the run. A cancel of a run that already ended returns the run unchanged.

Handle errors

A request error returns at once, and Engine creates no run. The checks are the same as for Answer.

ErrorCause

400 invalid_request

A field is unknown or invalid, the mode is not available, or principals does not match the collection’s access_control.

400 invalid_filter

The filter has an unsupported field, operator, or value.

403 forbidden

The key does not have the query action. Or a key that is not a cluster key sent recall, web_search: "use_if_needed", or query_image.

404 not_found

The key cannot see the collection, or the collection is being deleted.

409 collection_empty

The collection has no files.

409 collection_not_ready

No content is ready. When retryable is true, wait for processing. When it is false, correct the failed files first.

409 idempotency_conflict

The Idempotency-Key was used before with a different body.

413 request_too_large

The body is larger than 6,291,456 bytes.

503 temporarily_unavailable

A dependency is not available. Retry with the same Idempotency-Key.

A run that starts can still end failed. Its error then holds code, message, and retryable. The research.updated event holds the code in data.error_code.

200 OK · failed

{
  "id": "rsr_01J8Z3K4N5P6Q7R8S9T0V1W2X3",
  "namespace": "acme",
  "collection": "incident-logs",
  "status": "failed",
  "mode": "ultra",
  "progress": {
    "stage": "exploring",
    "label": "Exploring your files",
    "step": 2,
    "total_steps": 3
  },
  "answer": null,
  "sources": null,
  "image_description": null,
  "usage": null,
  "error": {
    "code": "temporarily_unavailable",
    "message": "The research ran longer than 600 seconds. It is not billed. Start it again.",
    "retryable": true
  },
  "created_at": "2026-10-06T12:02:00Z",
  "updated_at": "2026-10-06T12:12:00Z",
  "completed_at": "2026-10-06T12:12:00Z"
}

A run that passes its time limit ends failed with temporarily_unavailable, and Engine does not bill it. A run that the cluster stops, for example during a deploy, ends the same way. When retryable is true, start a new run with a new Idempotency-Key. The same key returns the same failed run.

Retention

Engine keeps a run for 7 days after it ends. A daily cleanup then deletes it, and a read returns 404 not_found. Store the answer and sources in your application if you need them for longer.

See Create research, Get research, and Cancel research for every field and error.

Was this page helpful?