---
title: Answer collection
description: Generate an answer with citations from files in one collection.
doc_version: 0.1.0-preview
last_updated: 2026-09-14
---

# Answer collection

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

Generate an answer with citations from files in one collection.

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

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

Answer runs one retrieve-and-answer pass. effort is medium (the default) or high. Both allow 45 seconds, and 45 more with a query image. For deep, many-step work, start a research run. GET /info lists the supported efforts.

Send prior turns in history. Engine does not store conversations.

sources is a list in ref order: the cited sources first, in the order the answer first cites them, then the others by score. A marker such as [[SRC:0001]] in answer names the source whose ref is SRC:0001. sources: cited lists only the cited sources. sources: none returns an empty list and an answer with no markers.

Send principals only when access_control is enforced. Access rules bound every source used for the answer.

An empty collection returns collection_empty. A failed collection returns collection_not_ready with retryable: false.

## Example request

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

{
  "query": "What caused the database timeout?",
  "effort": "medium",
  "sources": "cited"
}
```

## 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

### `effort`

**string · optional**

medium (the default) or high. high reads more and takes longer. Both allow 45 seconds. For deep, many-step work, start a research run.

Default: `"medium"`.

- `medium`: One retrieve-and-answer pass. The default.
- `high`: One retrieve-and-answer pass that reads more and takes longer.

### `filter`

**FilterClause · optional · nullable**

Structured selection of files to use for the answer. Uses the same grammar as Search. Filters do not replace access rules.

- 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.

### `history`

**array<object> · optional**

Prior conversation turns, oldest first. Maximum 32 messages. Your application stores and resends this history.

- Maximum items: 32

#### `history[].content`

**string · required**

Text of this previous conversation turn. Maximum 32,000 characters.

- Maximum characters: 32000

#### `history[].role`

**string · required**

Speaker of this prior turn: user or assistant. Engine does not store a conversation.

- `user`: A previous user message.
- `assistant`: A previous assistant answer.

### `instructions`

**string · optional · nullable**

Style instructions for the answer, such as its language, tone, or format. At most 8,000 characters. They replace the default answer style, not the rules for using sources.

- Maximum characters: 8000

### `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**

Question to answer from this collection. Must contain non-space text and be at most 32,000 characters.

- Minimum characters: 1
- Maximum characters: 32000

### `query_image`

**object · optional · nullable**

An image to ask about, read for this query only. Engine does not store or index it. Needs a cluster key, and adds 45 seconds to the time limit.

#### `query_image.data`

**string · required**

The image as base64 with no data-URI prefix. It decodes to at most 4 MiB.

#### `query_image.media_type`

**string · required**

Image format: image/png, image/jpeg, or image/webp.

- `image/png`: PNG image.
- `image/jpeg`: JPEG image.
- `image/webp`: WebP image.

### `sources`

**string · optional**

Which sources the response lists: all (default), cited, or none. none also removes the citation markers from the answer.

Default: `"all"`.

- `all`: List the cited sources first, then the other retrieved sources.
- `cited`: List only the cited sources.
- `none`: List no sources, and remove the citation markers from the answer.

## Responses

### 200 · Success

An answer with source markers and its sources in ref order.

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",
  "answer": "Checkout requests stalled because the database connection pool was exhausted [[SRC:0001]].",
  "effort": "medium",
  "sources": [
    {
      "ref": "SRC:0001",
      "span": {
        "file_id": "fil_01K6Q6N7D8E9F0G1H2J3K4M5N6",
        "unit": "chars",
        "start": 0,
        "end": 77
      },
      "path": "/reports/incident.txt",
      "content_type": "text/plain",
      "score": 0.81,
      "cited": true
    }
  ],
  "usage": {
    "credits": 1
  }
}
```

#### Response fields

##### `answer`

**string · required**

Answer text. A citation marker such as [[SRC:0001]] names the source whose ref is SRC:0001. The answer has no markers when the request sets sources to none.

##### `effort`

**string · required**

The effort that produced this answer.

- `medium`: One retrieve-and-answer pass. The default.
- `high`: One retrieve-and-answer pass that reads more and takes longer.

##### `image_description`

**string · optional · nullable**

How Engine read the query image. Null when the request had no image.

##### `request_id`

**string · required**

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

##### `sources`

**array<object> · required**

The sources in ref order: the cited sources first, in the order the answer first cites them, then the others by score. Empty when the request sets sources to none.

###### `sources[].cited`

**boolean · required**

Whether the answer cites this source.

###### `sources[].content_type`

**string · required**

Content type of the source file.

###### `sources[].file`

**object · optional · nullable**

Not returned yet. The whole stored file.

###### `sources[].file.content_type`

**string · required**

Content type of the part.

###### `sources[].file.data`

**string · optional · nullable**

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

###### `sources[].file.expires_at`

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

ISO 8601 UTC time after which the URL expires.

###### `sources[].file.page`

**integer · optional · nullable**

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

###### `sources[].file.seconds`

**number · optional · nullable**

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

###### `sources[].file.size_bytes`

**integer · required**

Size of the part in bytes, before base64.

###### `sources[].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.

###### `sources[].matches`

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

Not returned yet. On a cited source, one match: the passage that supports the answer.

###### `sources[].matches[].highlight`

**object · optional · nullable**

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

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

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

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

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

**string · required**

About 300 characters around the match.

###### `sources[].matches[].raw`

**object · optional · nullable**

The match cut from the file in its own format.

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

**string · required**

Content type of the part.

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

**string · optional · nullable**

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

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

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

ISO 8601 UTC time after which the URL expires.

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

**integer · optional · nullable**

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

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

**number · optional · nullable**

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

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

**integer · required**

Size of the part in bytes, before base64.

###### `sources[].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.

###### `sources[].matches[].screenshots`

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

Images of the match.

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

**string · required**

Content type of the part.

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

**string · optional · nullable**

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

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

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

ISO 8601 UTC time after which the URL expires.

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

**integer · optional · nullable**

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

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

**number · optional · nullable**

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

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

**integer · required**

Size of the part in bytes, before base64.

###### `sources[].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.

###### `sources[].matches[].span`

**object · required**

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

###### `sources[].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.

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

**number · required**

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

- Minimum: 0
- Maximum: 1

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

**number · required**

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

- Minimum: 0
- Maximum: 1

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

**number · required**

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

- Minimum: 0
- Maximum: 1

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

**number · required**

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

- Minimum: 0
- Maximum: 1

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

**integer · optional · nullable**

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

- Minimum: 0
- Maximum: 2147483647

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

**string · required**

ID of the file within the collection.

###### `sources[].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.

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

**integer · required**

First page after the range.

- Minimum: 1

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

**integer · required**

First page of the range, from 1.

- Minimum: 1

###### `sources[].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

###### `sources[].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.

###### `sources[].matches[].text`

**string · optional · nullable**

The text of the match.

###### `sources[].path`

**string · required**

Logical path of the source file within the collection.

###### `sources[].ref`

**string · required**

The marker of this source without its brackets, such as SRC:0001. It stands in answer as [[SRC:0001]].

###### `sources[].score`

**number · required**

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

###### `sources[].span`

**object · required**

Place in the source file. Send it to Read spans to get its content.

###### `sources[].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.

###### `sources[].span.bbox.bottom`

**number · required**

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

- Minimum: 0
- Maximum: 1

###### `sources[].span.bbox.left`

**number · required**

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

- Minimum: 0
- Maximum: 1

###### `sources[].span.bbox.right`

**number · required**

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

- Minimum: 0
- Maximum: 1

###### `sources[].span.bbox.top`

**number · required**

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

- Minimum: 0
- Maximum: 1

###### `sources[].span.end`

**integer · optional · nullable**

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

- Minimum: 0
- Maximum: 2147483647

###### `sources[].span.file_id`

**string · required**

ID of the file within the collection.

###### `sources[].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.

###### `sources[].span.pages.end`

**integer · required**

First page after the range.

- Minimum: 1

###### `sources[].span.pages.start`

**integer · required**

First page of the range, from 1.

- Minimum: 1

###### `sources[].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

###### `sources[].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.

##### `usage`

**object · required**

What the query used.

###### `usage.credits`

**integer · required**

Credits metered for this query.

## 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": {}
  }
}
```

### 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_empty`

The collection contains no living files to answer from.

Add a file and wait for a ready event before querying.

```json
{
  "error": {
    "code": "collection_empty",
    "message": "The collection contains no living files to answer from.",
    "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:answer" \
  -H "Authorization: Bearer $GRAPHON_ENGINE_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "query": "What caused the database timeout?",
  "effort": "medium",
  "sources": "cited"
}'
```

### Python

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

request = urllib.request.Request(
    os.environ["GRAPHON_ENGINE_URL"] + "/namespaces/acme/collections/incident-logs:answer",
    data=json.dumps({"query": "What caused the database timeout?", "effort": "medium", "sources": "cited"}).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:answer", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GRAPHON_ENGINE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"query":"What caused the database timeout?","effort":"medium","sources":"cited"}),
});
console.log(response.status, await response.text());
```

## Sitemap

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