Overview

The agent surface is a small, stateful layer on top of the rest of the v2 API: a session is a conversation, and each question you ask it is a turn. Every turn runs asynchronously, the same way as every other v2 operation — you get back a job_id immediately and poll GET /v2/jobs/{job_id} for the answer.
Examples use the default base URL, https://api.orion.file.ai/prod/v1. If your workspace is on an instance-specific host, swap the hostname and change nothing else — see Switching between instances.

Create a session

POST /v2/agent/sessions

List sessions

GET /v2/agent/sessions

Get a session

GET /v2/agent/sessions/{session_id}

Ask a question

POST /v2/agent/sessions/{session_id}/ask

Stop the running turn

POST /v2/agent/sessions/{session_id}/stop

Read the conversation

GET /v2/agent/sessions/{session_id}/messages

Delete a session

DELETE /v2/agent/sessions/{session_id}

Poll a job

GET /v2/jobs/{job_id}

Authentication

Every route on this page uses the same x-api-key header as the rest of the API — see About the fileAI API.
The agent additionally requires the calling key to be associated with a user. A key with no user behind it gets a 403 FORBIDDEN on session creation and on every ask — re-issue the key with a user rather than retrying.

Idempotency

Three of these routes are write operations that carry a required Idempotency-Key header, not merely an optional one: The header being required here (rather than optional, as on most of the API) is deliberate: a session opened twice because a POST /v2/agent/sessions retry timed out, or a turn asked twice because an ask retry raced the original, are both mistakes an idempotency key exists to prevent. See Idempotent requests for the general replay mechanics — the 24h window, the Idempotent-Replayed header, and what counts as “the same body” all apply here unchanged.
ask and stop are also serialized by a separate rule that has nothing to do with idempotency: a session runs one turn at a time. Asking again while a run is live is a 409 AGENT_RUN_IN_PROGRESS, independent of whether you sent a fresh Idempotency-Key.

The async job lifecycle

Every turn follows the same three steps as the rest of the v2 API’s async operations:
1

ask

POST /v2/agent/sessions/{session_id}/ask adds the turn to the session and returns 202 Accepted immediately — never an answer.
2

poll job_id

Poll the returned poll_urlGET /v2/jobs/{job_id} — until status reaches a settled value.
FAILED with error.code: "SUPERAGENT_RUN_STALE" is provisional: it means the run went quiet longer than we wait and was abandoned, not that the agent reported a failure. A run that was only slow can still finish afterwards and resolve to COMPLETED or STOPPED — including a second, corrected callback. Treat this one status as retryable-but-not-final if you keep your own records per job_id.
3

or receive the callback

If the API key has a callback URL configured, the settled job is also POSTed there — you don’t have to poll if you’d rather be notified. Poll poll_url as a fallback regardless: a webhook delivery can be delayed or lost, while the job itself is durable.
GET /v2/jobs/{job_id} is shared with other v2 operations. Branch on kind before reading the rest of the body — an agent job (kind: "agent", id prefixed sajob_) reports the statuses above and carries result.answer; other kinds report their own statuses and results.

Listing and reading sessions

GET /v2/agent/sessions is cursor-paginated like the rest of the API’s list endpoints — see Pagination for the general cursor mechanics. Rows are newest first and deliberately omit run_status: resolving it costs a query per session, so read a single session (GET /v2/agent/sessions/{session_id}) when you actually need to know whether it can take a turn.
run_status is how you tell whether the session is free:
A run whose worker died sends no terminal event, so a run that stopped reporting is only reconciled to STALE when you read the session (or its messages) — reading is what frees a session stuck behind a dead run.

Reading the conversation

GET /v2/agent/sessions/{session_id}/messages returns turns oldest first, one row per turn, cursor-paginated with a smaller default page (20, vs. 50 elsewhere) because a turn carries a whole answer. An answer longer than 256KB is returned truncated, with content_truncated: true and content_length giving its real size — fetch the turn’s job_id via GET /v2/jobs/{job_id} to read it in full.

Stopping a run

POST /v2/agent/sessions/{session_id}/stop asks the agent to end the live run and returns immediately with stop_requested. It is a request, not a completion:
  • The agent keeps working for about a second while it checkpoints its partial work, then ends the run itself — the answer produced so far is kept, and the time and cost already incurred are still billed.
  • A stop always races the run it is ending: if the run had already finished, the response reports stop_requested: false and nothing was forwarded. This is a normal outcome, not an error.
  • The session stays open — the next turn continues from the checkpoint the agent wrote.
Poll the returned job_id to see the run reach STOPPED.

Deleting a session

DELETE /v2/agent/sessions/{session_id} deletes the session, stopping a live run first rather than refusing the delete. stopped_run in the response tells you whether that happened. Nothing is lost: the agent still ends the run properly, and its job lives outside the session — the partial answer, the real cost, and any callback all still arrive via GET /v2/jobs/{job_id}. The turns themselves are retained but become unreachable; every route on a deleted session, including DELETE again, returns 404.