# Agent sessions
Source: https://docs.file.ai/ask/agent-sessions

> Hold a conversation with the fileAI agent over your workspace documents, one async turn at a time.

Agents

# Agent sessions

Copy pageCopy page

Hold a conversation with the fileAI agent over your workspace documents, one async turn at a time.

Copy pageCopy page

**Minto** is fileAI’s AI assistant: it answers questions about the documents in your workspace. The agent sessions API is how you use Minto from your own code. You open a session, ask a question, and get back a job that you poll (or receive by callback) for the answer. Later questions in the same session build on earlier ones.

## 

[​

](#how-it-works)

How it works

A **session** holds the conversation. Each question you ask is a **turn**, and each turn runs as an asynchronous **agent job** with an id that starts `sajob_`. Agent jobs are polled on the same route as document jobs; see [Async jobs](/api/async-jobs#agent-jobs). A session runs **one turn at a time**. Asking while a turn is still `QUEUED` or `RUNNING` returns `409 AGENT_RUN_IN_PROGRESS`; wait for it to settle, or stop it.

### 

[​

](#requirements)

Requirements

*   Every route uses the usual `x-api-key` header. See [Authentication](/authentication).
*   The key must be associated with a user. A key with no user gets `403 FORBIDDEN` on session creation and on every `ask`. Re-issue the key with a user rather than retrying.
*   `POST /v1/agent/sessions`, `POST …/ask` and `POST …/stop` **require** an `Idempotency-Key` header. Without it you get `400`. `DELETE /v1/agent/sessions/{session_id}` accepts one optionally. See [Idempotent requests](/api/idempotency).

### 

[​

](#endpoints)

Endpoints

Method and path

What it does

`POST /v1/agent/sessions`

Create a session. Returns `201`.

`GET /v1/agent/sessions`

List sessions, newest first.

`GET /v1/agent/sessions/{session_id}`

Read one session, including `run_status`.

`POST /v1/agent/sessions/{session_id}/ask`

Ask a question. Returns `202` with a job.

`POST /v1/agent/sessions/{session_id}/stop`

Ask the agent to stop the running turn. Returns `202`.

`GET /v1/agent/sessions/{session_id}/messages`

Read the conversation, oldest first.

`DELETE /v1/agent/sessions/{session_id}`

Delete the session, stopping a live turn first.

`GET /v1/jobs/{job_id}`

Poll a turn’s job.

## 

[​

](#example)

Example

The cURL commands assume `FILEAI_BASE_URL="https://api.orion.file.ai/prod/v1"`, or your [regional host](/authentication#regions).

1

Create a session

```
curl -X POST "$FILEAI_BASE_URL/agent/sessions" \
  -H "x-api-key: $FILEAI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Q3 supplier invoices" }'
```

Response (201)

```
{
  "session_id": "665f1c2e8a1b4c0012ab34cd",
  "title": "Q3 supplier invoices",
  "created_at": "2026-09-14T09:00:00.000Z",
  "updated_at": "2026-09-14T09:00:00.000Z",
  "last_active_at": null
}
```

`title` is optional (up to 200 characters). Without it the session is called `New Chat` until the agent names it from the first turn.

2

Ask a question

cURL

Python

Node.js

```
curl -X POST "$FILEAI_BASE_URL/agent/sessions/665f1c2e8a1b4c0012ab34cd/ask" \
  -H "x-api-key: $FILEAI_API_KEY" \
  -H "Idempotency-Key: 0f1c8e03-978e-40d5-bc93-6894a57f9324" \
  -H "Content-Type: application/json" \
  -d '{
    "user_prompt": "Which invoices from Acme are still unpaid?",
    "directory_id": "665f1c2e8a1b4c0012ab9911",
    "client_ref": "order-4417"
  }'
```

```
import os, uuid, requests

BASE = "https://api.orion.file.ai/prod/v1"
HEADERS = {"x-api-key": os.environ["FILEAI_API_KEY"]}

job = requests.post(
    f"{BASE}/agent/sessions/{session_id}/ask",
    headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
    json={
        "user_prompt": "Which invoices from Acme are still unpaid?",
        "directory_id": "665f1c2e8a1b4c0012ab9911",
        "client_ref": "order-4417",
    },
).json()
```

```
const BASE = "https://api.orion.file.ai/prod/v1";
const headers = { "x-api-key": process.env.FILEAI_API_KEY };

const job = await fetch(`${BASE}/agent/sessions/${sessionId}/ask`, {
  method: "POST",
  headers: {
    ...headers,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    user_prompt: "Which invoices from Acme are still unpaid?",
    directory_id: "665f1c2e8a1b4c0012ab9911",
    client_ref: "order-4417",
  }),
}).then((r) => r.json());
```

Response (202)

```
{
  "job_id": "sajob_9f2c1a4b7e8d4c1fa0b3",
  "status": "QUEUED",
  "session_id": "665f1c2e8a1b4c0012ab34cd",
  "created_at": "2026-09-15T02:14:00.000Z",
  "poll_url": "https://api.orion.file.ai/prod/v1/jobs/sajob_9f2c1a4b7e8d4c1fa0b3"
}
```

3

Poll the job

```
curl "$FILEAI_BASE_URL/jobs/sajob_9f2c1a4b7e8d4c1fa0b3" \
  -H "x-api-key: $FILEAI_API_KEY"
```

Response

```
{
  "job_id": "sajob_9f2c1a4b7e8d4c1fa0b3",
  "kind": "agent",
  "status": "COMPLETED",
  "session_id": "665f1c2e8a1b4c0012ab34cd",
  "created_at": "2026-09-15T02:14:00.000Z",
  "updated_at": "2026-09-15T02:14:11.000Z",
  "settled_at": "2026-09-15T02:14:11.000Z",
  "client_ref": "order-4417",
  "result": {
    "answer": "Two Acme invoices are unpaid: INV-2291 ($4,200) and INV-2309 ($1,150).",
    "title": "Unpaid Acme invoices",
    "execution_time": 8.7,
    "execution_cost": 0.021,
    "files": []
  },
  "error": null
}
```

If your API key has a default callback URL, the settled job is also POSTed there. Keep polling as a fallback; see [Async jobs](/api/async-jobs#callbacks).

## 

[​

](#reference)

Reference

### 

[​

](#ask-fields)

Ask fields

Field

Type

Description

`user_prompt`

string, **required**

The question. 1 to 30,000 characters.

`document_ids`

string\[\]

Limit this turn to these documents. Up to 100. Takes precedence over `file_ids`.

`file_ids`

string\[\]

Limit this turn to these files; their documents are resolved for you. Up to 100.

`directory_id`

string

Limit this turn to one folder. Must be a valid id.

`use_all_files`

boolean

Search the workspace’s processed documents instead of a fixed list. Overrides `file_ids` and `document_ids`. Default `false`.

`llm_model`

string

Model hint for this turn. Defaults to the workspace’s configured model.

`allow_internet_search`

boolean

Let the agent search the public internet. Default `true`.

`allow_ai_query_subagent`

boolean

Let the agent run structured queries over your data. Default `true`.

`client_ref`

string

Your own reference, up to 255 characters. Echoed on the job and on its callback.

`language_code`

string

BCP-47 tag for the answer language, e.g. `en`. Up to 35 characters.

Omit `document_ids`, `file_ids` and `use_all_files` to keep the session’s own scope.

`use_all_files` only considers the **100 most recently created** processed documents. In a larger workspace the agent does not see the rest. When coverage matters, name the documents with `document_ids` or `file_ids`.

### 

[​

](#job-result)

Job result

`GET /v1/jobs/{job_id}` returns `kind: "agent"` for a `sajob_` id. For what each status means (`QUEUED`, `RUNNING`, `COMPLETED`, `STOPPED`, `FAILED`), see [Agent jobs](/api/async-jobs#agent-jobs). Two things matter for Minto in particular:

*   `result.answer` holds the answer on `COMPLETED`, and the partial answer on `STOPPED`. On `FAILED`, `error.code` and `error.detail` describe the failure, and `error.retryable` says whether asking again is worthwhile.
*   **`FAILED` with `error.code` `SUPERAGENT_RUN_STALE` is provisional.** The run went quiet for too long and was given up on, but a run that was only slow can still finish and move to `COMPLETED` or `STOPPED`, with a second callback. Treat it as safe to retry, but don’t close out your own record for that `job_id` on it.

`result.files` lists any files the agent generated (PDF, HTML or Excel), each with `name`, `content_type`, a presigned `url` and its `expires_at`. Use the URL as given; ask again for a fresh link once it expires.

### 

[​

](#session-run-status)

Session run status

`GET /v1/agent/sessions/{session_id}` includes `run_status` and the latest `job_id`. The list endpoint leaves `run_status` out, so read the single session when you need to know whether it can take a turn.

`run_status`

Session state

`null`

Never run. Free.

`QUEUED`, `RUNNING`

A turn is in flight. `ask` returns `409`.

`COMPLETED`, `FAILED`, `STOPPED`

Free.

`STALE`

Free. The last turn stopped reporting and was cleaned up when the session was read.

A turn that stops reporting sends no final event. It is only marked `STALE` when you read the session or its messages, or ask a new question; that is what frees the session.

### 

[​

](#reading-the-conversation)

Reading the conversation

`GET /v1/agent/sessions/{session_id}/messages` returns **one row per message**, oldest first: a `user` row for each question and an `assistant` row for each answer, so a turn appears as two rows.

Field

Description

`message_id`

The message’s id.

`role`

`user` or `assistant`.

`content`

The question on a `user` row; the answer on an `assistant` row. Empty while the turn is still running.

`content_truncated`

`true` when the answer was longer than 262,144 characters (256 × 1024) and `content` holds only the first part.

`content_length`

The answer’s full length in characters.

`run_status`

The turn’s status on an `assistant` row; `null` on a `user` row.

`job_id`

The turn’s job, or `null` for a turn asked in the app.

`error`

`code` and `detail` when the turn failed.

`execution_time`, `execution_cost`

Seconds taken and model spend in USD.

`files`

Files the agent generated on this turn.

`created_at`, `updated_at`

Timestamps.

To read a truncated answer in full, fetch its `job_id` from `GET /v1/jobs/{job_id}`. A turn asked in the app has no job, so its truncated answer can’t be retrieved in full through the API.

### 

[​

](#pagination)

Pagination

Both list routes use [cursor pagination](/api/pagination) only; there is no `page` parameter. Omit `cursor` for the first page, then pass `pagination.nextCursor` until `hasMore` is `false`.

Route

Order

Default `limit`

Maximum

`GET /v1/agent/sessions`

Newest first

50

100

`GET …/{session_id}/messages`

Oldest first

20

50

A larger `limit` is reduced to the maximum.

### 

[​

](#stopping-a-turn)

Stopping a turn

`POST /v1/agent/sessions/{session_id}/stop` is a request, not a completion. It returns `202` straight away with `stop_requested`, the `run_status` at that moment, `job_id` and `poll_url`.

*   The agent keeps working for about a second while it saves its partial work, then ends the turn. The partial answer is kept, and the time and cost already spent are still billed.
*   If the turn had already finished, `stop_requested` is `false`. That is a normal outcome, not an error.
*   The session stays open, and the next turn continues from where the agent stopped.

Poll the `job_id` until it reaches `STOPPED`, or whatever status the turn reached on its own.

### 

[​

](#deleting-a-session)

Deleting a session

`DELETE /v1/agent/sessions/{session_id}` stops a live turn first instead of refusing. The response has `deleted: true`, `stopped_run` (whether a turn was stopped) and that turn’s `job_id`. The job outlives the session: its partial answer, cost and callback still arrive through `GET /v1/jobs/{job_id}`. Every route on a deleted session, including another `DELETE`, returns `404`.

## 

[​

](#related)

Related

## Async jobs

How agent jobs, document jobs and uploads report progress.

## Idempotent requests

Why create, ask and stop require an `Idempotency-Key`.

## Pagination

Cursor rules for the session and message lists.

## Errors

`AGENT_RUN_IN_PROGRESS`, `FORBIDDEN` and the rest.

[Previous](/examples/warehouse-sync)[

Folders

Group files into folders in the app or with the directories API, without duplicating them.

Next

](/ingest/folders)

⌘I

## On this page

*   [How it works](#how-it-works)
    *   [Requirements](#requirements)
    *   [Endpoints](#endpoints)
*   [Example](#example)
*   [Reference](#reference)
    *   [Ask fields](#ask-fields)
    *   [Job result](#job-result)
    *   [Session run status](#session-run-status)
    *   [Reading the conversation](#reading-the-conversation)
    *   [Pagination](#pagination)
    *   [Stopping a turn](#stopping-a-turn)
    *   [Deleting a session](#deleting-a-session)
*   [Related](#related)
