AI & Chat RAG-powered chat, semantic search, notes, metadata extraction, and auto-summarization

Base URL: https://api.fast.io/current/ Auth: Bearer {jwt_token}

AI agent endpoint paths (/ai/agent/): the AI agent endpoints are served under /ai/agent/, which is the canonical path family. The older /ai/chat/ paths remain a legacy alias — every agent endpoint still resolves through the /ai/chat/ anchor to the exact same handler, so an older integration calling /ai/chat/... continues to work unchanged.

New integrations should use /ai/agent/.... If you migrate an existing /ai/chat/... integration, swap only the URL family — the request parameters, response shapes, and behavior are identical between the two anchors because they reach the same code.

Overview

Fastio provides built-in AI capabilities for workspaces and shares:

Fastio's AI is a full agent, not a read-only Q&A tool. In addition to reading, analyzing, searching, and answering questions about your files, it can take actions on your behalf — for example, creating documents and notes and organizing your content. It always acts within your own permissions and plan entitlements, and its actions consume credits like any other operation.

Plan Requirements

AI capabilities are gated by a set of billing-plan features:

Plan coverage:

Plancontent_aiai_agentEffect
StarterononFull AI (chat, intelligence, RAG)
BusinessononFull AI (chat, intelligence, RAG)
GrowthononFull AI (chat, intelligence, RAG)

All current paid plans (Starter, Business, and Growth) include both content_ai and ai_agent, so full AI — chat, intelligence, and RAG — is available on every plan a new organization can choose. New organizations choose one of these paid plans.

How gated endpoints respond when a plan lacks the feature:

New organizations — for humans and AI agents alike — choose a paid plan (Starter, Business, or Growth), each of which includes both content_ai and ai_agent and unlocks full agentic chat, intelligence, and RAG. See the Organizations reference for plan selection.

Intelligence Setting

The intelligence boolean on a workspace or portal share controls whether uploaded files are automatically indexed for RAG.

Shared folder restriction: Intelligence is only available on portal shares (independent storage). Workspace folder shares (storage_mode=workspace_folder) cannot have intelligence enabled — their files are indexed through the parent workspace instead. The API will return an error if you attempt to enable intelligence on a shared folder share.

Enable at creation:

POST /current/org/{org_id}/create/workspace/

Pass intelligence=true

Update later:

POST /current/workspace/{workspace_id}/update/

Pass intelligence=true

Note: Intelligence can be enabled and disabled within time restrictions. Disabling intelligence destroys indexed embeddings (the vector index is flushed). Re-enabling intelligence incurs re-indexing costs as AI credits are consumed to re-index all files. When a plan loses ai_agent (e.g. on downgrade), the RAG indexing pipeline stops even if the instance flag remains set; previously indexed embeddings remain but will not be updated.

How the Agent Uses Files

There is no chat “type” to choose — a single agent surface handles every conversation. You do not pass a type (or personality) parameter; the agent adapts to what you send:

A single agent turn can combine all of the above — general reasoning, RAG over the indexed scope, and directly attached files.

Attaching Files and Folders

Give the agent specific files or folders as context by including them as reference items in the references, content_parts, or subjects array of a create-chat or send-message request. You do not assemble a file’s metadata yourself — send only the node id (and, for a file, an optional version id), and Fastio resolves the full details server-side after verifying your access and the file’s AI-readiness.

subjects pins objects from inside the workspace/share as the turn’s focus; a separate uploads array carries files staged from outside the scope. content_parts is an ordered stream that interleaves text segments with inline reference pills (the same item shape). All three of references, content_parts, and subjects are resolved and gated by the rules below.

File reference

{ "type": "file", "id": "{node_id}", "file_details": { "node_id": "{node_id}", "version_id": "{version_id}" } }

Folder reference

{ "type": "folder", "id": "{node_id}", "folder_details": { "node_id": "{node_id}" } }

Limits

Validation is strict — a bad reference fails the request

If a referenced file or folder cannot be attached — it does not exist, you cannot access it, it has been deleted, or it is not AI-eligible — the request is rejected with an error, not silently dropped:

Choosing what to attach

Use CaseWhat to attach
Analyze specific files directlyFile reference items in references / content_parts / subjects
Ground answers in a folder’s indexed files (with citations)Folder reference items (requires intelligence enabled)
Ask general questions across all indexed filesNothing — the agent may search the whole indexed scope
General conversation, no filesNothing

AI State (File Readiness)

Files in an intelligent workspace progress through AI processing states:

StateMeaning
disabledIntelligence not enabled for this file/workspace
pendingQueued for AI processing
in_progressCurrently being processed by AI
readyFile can be used with AI chat (attached directly or via scope). The file has been processed enough (e.g., preview/summary generated) to be usable in AI conversations.
indexedFile contents (for documents) have been indexed via RAG. This state is used when intelligence is enabled on the workspace/share. Indexed files are searchable by semantic meaning and their content is used as grounding in scoped AI chats.
failedAI processing failed

Files with ai_state: ready can be used with AI chat. Files with ai_state: indexed have additionally had their contents indexed for RAG-powered semantic search. When intelligence is enabled on a workspace/share, files progress to indexed automatically. Check a file’s AI state in the ai.state field of storage list or file details responses.

Controlling Response Length and Style

There is no personality parameter. Control verbosity and style directly in your question phrasing:

Advanced Per-Turn Fields

Beyond question and the file-reference arrays, a create-chat or send-message request accepts these optional per-turn fields. All are optional; omit them for normal use.

FieldTypeDescription
uploadsJSON arrayFocus files staged from outside the workspace/share (as opposed to subjects, which pins objects from inside it).
viewJSON objectA snapshot of the caller’s current UI view, so the agent can reason about what the user is looking at.
activityJSON arrayRecent-activity entries giving the agent short-term context.
role_in_orgstringThe acting user’s free-text role in the organization, used to tailor the response.
idempotency_keystringClient-supplied replay guard (max 64 chars). Re-sending the same key returns the already-created turn instead of creating a duplicate. Omit to have one generated.

The acting user’s identity is taken from your Bearer token — it is never read from the request body, so a request cannot spoof who the turn runs as.

Notes (Stored Knowledge)

Notes are a storage node type (like files and folders) that store markdown content directly on the server. They appear in storage listings with "type": "note" and "mimetype": "text/markdown". Notes are workspace-only — they cannot be created in shares.

Why Notes Matter

In an intelligent workspace, notes are ingested and indexed just like uploaded files. This makes them a way to bank knowledge over time — store interesting facts, research findings, decisions, or project context. In future AI chats that scope the entire workspace (or include the note’s folder), the note content will be used as grounding when the AI searches for relevant information.

Create a note

POST /current/workspace/{workspace_id}/storage/{parent_id}/createnote/
ParameterTypeRequiredDescription
namestringYesNote name, must end in .md
contentstringYesMarkdown content, max 100 KB

{parent_id} is a folder OpaqueId or "root". Returns the created note as a node resource.

Update a note

POST /current/workspace/{workspace_id}/storage/{node_id}/updatenote/
ParameterTypeRequiredDescription
namestringNoNew name, must end in .md
contentstringNoNew markdown content, max 100 KB, non-blank (an empty or whitespace-only value is rejected)

At least one of name or content must be provided. Updating content creates a new version.

Read note content

GET /current/workspace/{workspace_id}/storage/{node_id}/read/

Returns the raw markdown content.

Linking a user to a note

Workspace AI Endpoints

Create a new chat

POST /current/workspace/{workspace_id}/ai/agent/

Creates a thread and its first turn; the AI processes it asynchronously. There is no type or personality parameter — the request body is the initial question plus optional file references and the thread create-time fields.

Auth: Bearer token required. Workspace view permission. content_ai and ai_agent plan features required.

ParameterTypeRequiredDefaultDescription
questionstringYesInitial question, 1–32,000 characters. (May be omitted only when content_parts carries the message text.)
privacystringNoprivateprivate or public. public is currently disabled platform-wide — a privacy=public request returns 403 Forbidden; see “Publish a private chat” below.
namestringNoAuto-generatedChat name. A default is used if omitted.
kindstringNouseruser or agent. agent flags the chat as agentic. Set at creation, immutable thereafter.
referencesJSON arrayNoFile/folder reference items to attach as context — each a {type, id} file or folder item (see Attaching Files and Folders). Up to 20 files / 200 MB / 100 references; the backend resolves each item’s full details server-side.
content_partsJSON arrayNoOrdered content stream — text segments plus inline file/folder reference pills (same item shape as references).
subjectsJSON arrayNoFile/folder reference items pinned as focus subjects for the turn (same item shape as references).
uploadsJSON arrayNoFocus files staged from outside the workspace.

Also accepts the optional view, activity, role_in_org, and idempotency_key fields (see Advanced Per-Turn Fields above).

Request example:

curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "question=What were the Q3 revenue figures?" \
  -d "privacy=private"

Response (200 OK):

{
  "result": true,
  "thread": {
    "thread_id": "aBcDeFgHiJkLmNoPqR",
    "creator": { "type": "user", "id": "1234567890123456789" },
    "scope": { "type": "workspace", "id": "1234567890123456789" },
    "name": "New Chat",
    "status": "active",
    "kind": "user",
    "cost": { "credits": 0, "tokens": 0 },
    "privacy": { "visibility": "private", "owner": { "type": "user", "id": "1234567890123456789" } },
    "created_at": "2026-07-07 16:37:29 UTC",
    "updated_at": "2026-07-07 16:37:29 UTC"
  },
  "turn": {
    "turn_id": "xYzAbCdEfGhIjKlMnO",
    "thread_id": "aBcDeFgHiJkLmNoPqR",
    "seq": 1,
    "status": "pending",
    "idempotency_key": "3f2a…",
    "query": { "text": "What were the Q3 revenue figures?" },
    "error": null,
    "cost": { "credits": 0, "tokens": 0 },
    "created_at": "2026-07-07 16:37:29 UTC",
    "updated_at": "2026-07-07 16:37:29 UTC"
  }
}
FieldTypeDescription
thread.thread_idstringOpaque ID of the created thread (the chat). Use it as {chat_id} in the follow-up URLs below.
turn.turn_idstringOpaque ID of the initial turn (the first message). Use it as the {message_id} in the message-details / read URLs.
turn.statusstringInitial turn status — pending. The AI processes it asynchronously (see turn statuses under “Get message details”).

The full field lists are in Chat Session Object Schema (thread) and Message Object Schema (turn) below.

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Invalid privacy, kind, or name, or invalid question length
1609 (Not Found)404An attached file or folder reference does not exist or is not accessible
1605 (Invalid Input)406An attached reference is malformed, the wrong node type, or exceeds the 20-file / 200 MB / 100-reference limit
1700 (Forbidden)403privacy=public requested while public chats are disabled platform-wide
1660 (Conflict)409Thread still committing its first turn (retry with the same idempotency key), or the first message is too large to process
1664 (Datastore Error)500Thread or turn creation failed

List chats

GET /current/workspace/{workspace_id}/ai/agent/list/

Returns all chats created by the current user in the workspace. Sorted by most recently modified first.

Auth: Bearer token required. Workspace view permission. content_ai plan feature required.

Query parameters:

ParameterTypeRequiredDefaultDescription
kindstringNouserFilter by chat kind. Allowed values: user (only user-driven chats — the historical default), agent (only agentic chats), all (user + agent chats). Omit or pass user for backwards-compatible behavior.

Variant: Append /deleted to the path to list deleted chats: GET .../ai/agent/list/deleted

Request example:

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/list/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "chats": {
    "count": 1,
    "items": [
      {
        "thread_id": "aBcDeFgHiJkLmNoPqR",
        "creator": { "type": "user", "id": "1234567890123456789" },
        "scope": { "type": "workspace", "id": "1234567890123456789" },
        "name": "Quarterly report analysis",
        "status": "active",
        "kind": "user",
        "cost": { "credits": 15, "tokens": 1500 },
        "privacy": {
          "visibility": "private",
          "owner": { "type": "user", "id": "1234567890123456789" }
        },
        "created_at": "2026-07-07 16:00:00 UTC",
        "updated_at": "2026-07-07 16:30:05 UTC",
        "message_count": 5,
        "continuable": true,
        "latest_message": {
          "turn_id": "xYzAbCdEfGhIjKlMnO",
          "thread_id": "aBcDeFgHiJkLmNoPqR",
          "seq": 5,
          "status": "complete",
          "idempotency_key": "3f2a…",
          "query": { "text": "Summarize the quarterly report" },
          "error": null,
          "cost": { "credits": 3, "tokens": 300 },
          "created_at": "2026-07-07 16:30:00 UTC",
          "updated_at": "2026-07-07 16:30:05 UTC"
        }
      }
    ]
  }
}
FieldTypeDescription
chatsobjectCollection envelope {count, items}
chats.countintegerNumber of chat items returned in items
chats.itemsarrayArray of thread (chat) objects
chats.items[].thread_idstringOpaque ID of the thread (the chat)
chats.items[].creatorobject{type, id} — the chat creator
chats.items[].scopeobject{type, id} — the workspace or share the chat lives in
chats.items[].namestringChat display name
chats.items[].statusstringChat status
chats.items[].kindstringuser or agent. Always present; chats created before the field existed default to user.
chats.items[].message_countintegerTotal turns (messages) in the chat
chats.items[].continuablebooleantrue if the chat can be continued with a new message; false if the chat has no resumable conversation state (read-only history, e.g. a legacy chat migrated for history only). Omitted on create/update responses.
chats.items[].latest_messageobject/nullMost recent turn (see Message Object Schema), or null for an empty thread
chats.items[].cost.creditsintegerCredit charge for the chat (raw tokens converted at the meter rate)
chats.items[].cost.tokensintegerRaw token consumption the credit charge derives from
chats.items[].privacyobject{visibility, owner}
chats.items[].created_atstringCreation timestamp (YYYY-MM-DD HH:MM:SS UTC)
chats.items[].updated_atstringLast update timestamp (YYYY-MM-DD HH:MM:SS UTC)

Get chat details

GET /current/workspace/{workspace_id}/ai/agent/{chat_id}/details/

Returns chat details with full message history.

Auth: Bearer token required. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/details/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

The response is a thread object plus a separate turns collection (the message history) — there is no chat object and no embedded messages array.

{
  "result": true,
  "thread": {
    "thread_id": "aBcDeFgHiJkLmNoPqR",
    "creator": { "type": "user", "id": "1234567890123456789" },
    "scope": { "type": "workspace", "id": "1234567890123456789" },
    "name": "Quarterly report analysis",
    "status": "active",
    "kind": "user",
    "cost": { "credits": 15, "tokens": 1500 },
    "privacy": { "visibility": "private", "owner": { "type": "user", "id": "1234567890123456789" } },
    "created_at": "2026-07-07 16:00:00 UTC",
    "updated_at": "2026-07-07 16:30:05 UTC",
    "message_count": 3,
    "continuable": true
  },
  "turns": {
    "count": 1,
    "items": [
      {
        "turn_id": "xYzAbCdEfGhIjKlMnO",
        "thread_id": "aBcDeFgHiJkLmNoPqR",
        "seq": 1,
        "status": "complete",
        "idempotency_key": "3f2a…",
        "query": { "text": "Summarize the quarterly report" },
        "error": null,
        "cost": { "credits": 5, "tokens": 500 },
        "created_at": "2026-07-07 16:30:00 UTC",
        "updated_at": "2026-07-07 16:30:05 UTC"
      }
    ]
  }
}

The turns.items entries are the lightweight turn shape (no answer blob). Fetch a single turn’s full answer, citations, and action replay via Get message details below. turns is seq-ascending (oldest first).

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Chat not found or not accessible
1680 (Access Denied)401You do not have permission to access this thread

Update a chat

POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/update/

Update the name of an existing chat. The chat kind is set at creation and cannot be changed via this endpoint — any kind value supplied in the body is silently ignored.

Auth: Bearer token required. content_ai plan feature required.

ParameterTypeRequiredDescription
namestringYesNew chat name

Request example:

curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/update/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "name=Updated Chat Name"

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat not found or locked
1605 (Invalid Input)406Invalid name value
1664 (Datastore Error)500Update failed

Delete a chat

DELETE /current/workspace/{workspace_id}/ai/agent/{chat_id}/

Auth: Bearer token required. content_ai plan feature required.

Request example:

curl -X DELETE "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat not found or locked
1654 (Internal Error)500Chat in non-deletable state or internal error

Deleted chats can be listed via GET .../ai/agent/list/deleted.

Send a follow-up message

POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/message/

Send a new message to an existing chat. The message is processed asynchronously.

Auth: Bearer token required. content_ai and ai_agent plan features required.

ParameterTypeRequiredDefaultDescription
questionstringYesFollow-up question, 1–32,000 characters. (May be omitted only when content_parts carries the message text.)
referencesJSON arrayNoFile/folder reference items to attach as context — each a {type, id} file or folder item (see Attaching Files and Folders). Up to 20 files / 200 MB / 100 references; the backend resolves each item’s full details server-side.
content_partsJSON arrayNoOrdered content stream — text segments plus inline file/folder reference pills (same item shape as references).
subjectsJSON arrayNoFile/folder reference items pinned as focus subjects for the turn (same item shape as references).
uploadsJSON arrayNoFocus files staged from outside the workspace/share.

Also accepts the optional view, activity, role_in_org, and idempotency_key fields (see Advanced Per-Turn Fields above). There is no type parameter — the turn is appended to the existing thread.

Request example:

curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/message/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "question=How does that compare to Q2?"

Response (200 OK):

{
  "result": true,
  "turn_id": "mNoPqRsTuVwXyZaBcD",
  "thread_id": "aBcDeFgHiJkLmNoPqR",
  "seq": 2,
  "status": "pending",
  "idempotency_key": "7c1b…",
  "query": { "text": "How does that compare to Q2?" },
  "error": null,
  "cost": { "credits": 0, "tokens": 0 },
  "created_at": "2026-07-07 16:37:29 UTC",
  "updated_at": "2026-07-07 16:37:29 UTC"
}

The created turn’s fields are returned at the top level (not nested under a message object). The turn_id is the message id you poll or stream. status starts at pending; watch it reach a terminal state (see “Get message details”).

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Thread not found, not accessible, or locked
1680 (Access Denied)401You cannot message this thread; or (share chats only) folder attachment is not permitted for a restricted-view guest
1609 (Not Found)404An attached file or folder reference does not exist or is not accessible
1605 (Invalid Input)406An attached reference is malformed, the wrong node type, or exceeds the 20-file / 200 MB / 100-reference limit
1660 (Conflict)409The conversation has grown too large to continue — start a new chat
1664 (Datastore Error)500Transient storage error loading an attached file (retryable)
1654 (Internal Error)500Message creation or queuing failed

Cancel an in-progress message

POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/cancel/

Aborts an in-flight AI message instead of waiting for it to finish or time out. The worker stops streaming and the turn transitions to a cancelled terminal state, after which a new message can be sent immediately.

Auth: Bearer token required. Workspace view permission. content_ai plan feature required (the cancel endpoint does not require ai_agent, so a tier downgrade mid-stream does not strand the message).

Body: Empty.

Request example:

curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/ai/agent/{chat_id}/cancel/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{ "result": true }

The response is always { "result": true } — there is no success, message.id, or no_pending_message field. It is the same whether a pending turn was signalled or there was nothing in flight (idempotent no-op).

Behavior notes:

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat could not be loaded or the cancel signal could not be issued

List messages in a chat

GET /current/workspace/{workspace_id}/ai/agent/{chat_id}/messages/list/

Returns all messages in chronological order (oldest first).

Auth: Bearer token required. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/messages/list/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "messages": {
    "count": 1,
    "items": [
      {
        "turn_id": "xYzAbCdEfGhIjKlMnO",
        "thread_id": "aBcDeFgHiJkLmNoPqR",
        "seq": 1,
        "status": "complete",
        "idempotency_key": "3f2a…",
        "query": { "text": "Summarize the quarterly report" },
        "error": null,
        "cost": { "credits": 5, "tokens": 500 },
        "created_at": "2026-07-07 16:30:00 UTC",
        "updated_at": "2026-07-07 16:30:05 UTC"
      }
    ]
  }
}

Each item is the lightweight turn shape (no answer blob) — see Message Object Schema. Fetch a turn’s full answer and citations via Get message details.

FieldTypeDescription
messagesobjectCollection envelope {count, items}
messages.countintegerNumber of turn items returned in items
messages.itemsarrayArray of turn objects, ordered oldest-first (seq ascending)

A numeric path segment after /messages/list/ is a pagination offset (e.g. .../messages/list/50).

Get message details

GET /current/workspace/{workspace_id}/ai/agent/{chat_id}/message/{message_id}/details/

Retrieve detailed information about a specific message, including response text, citations, and cost.

Auth: Bearer token required. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/details/" \
  -H "Authorization: Bearer {jwt_token}"

The turn detail is returned under a message object on the workspace endpoint and a turn object on the share endpoint. It carries the full turn shape plus the decompressed answer result blob and the replayable actions list.

Key response fields (under message / turn):

FieldDescription
turn_idOpaque ID of this turn (message)
thread_idParent thread (chat) opaque ID
seqTurn sequence number within the thread
statusTurn status. One of pending, running, complete, failed, cancelled, lost, needs_input. (pending/running are non-terminal; the rest are terminal.)
idempotency_keyThe per-turn idempotency key
queryThe user’s submitted question: { text, content_parts?, references?, uploads?, subjects? } (internal server-only storage identifiers are stripped)
error{ message, grpc_status } on a failed/lost turn; null otherwise
cost{ credits, tokens } — the turn’s credit charge and raw token count
created_at / updated_atTimestamps (YYYY-MM-DD HH:MM:SS UTC)
resultThe decompressed answer blob (see below). null until the turn reaches a terminal state
actionsOrdered, replayable action cards (see below). Empty when the turn took no actions

Read the answer only when status is complete (or handle needs_input as a clarifying question — see the SSE needs_input event).

The result blob (when present) includes the answer text, references, citations (see Citation Format below), the thought_transcript / commentary_transcript strings and their ordered thought_events / commentary_events lists (each event { order, text, ts }), and — for a needs_input turn — a clarification object { type: "clarification", question }.

The actions list is ordered by seq — each entry has seq (order), label (human-readable name, e.g. "Create File"), state (running, done, failed, or cancelled), affected_refs (ids the action touched), and started_at / ended_at timestamps:

{
  "message": {
    "turn_id": "xYzAbCdEfGhIjKlMnO",
    "thread_id": "aBcDeFgHiJkLmNoPqR",
    "seq": 1,
    "status": "complete",
    "query": { "text": "Summarize the quarterly report" },
    "error": null,
    "cost": { "credits": 5, "tokens": 500 },
    "result": {
      "answer": "The quarterly report shows revenue growth of 15%...",
      "citations": [
        {
          "hash": "a1b2c3d4",
          "nodeId": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
          "versionId": "v1abc-defgh-ijklm",
          "entries": [
            { "page": 3, "snippet": "Revenue increased by 15% year over year...", "timestamp": null }
          ]
        }
      ],
      "thought_transcript": "",
      "commentary_transcript": "",
      "thought_events": [],
      "commentary_events": []
    },
    "actions": [
      {
        "seq": 1,
        "label": "Create File",
        "state": "done",
        "affected_refs": ["aBcDeFgHiJkLmNoPqR"],
        "started_at": "2026-07-07 16:37:29 UTC",
        "ended_at": "2026-07-07 16:37:30 UTC"
      }
    ],
    "created_at": "2026-07-07 16:37:00 UTC",
    "updated_at": "2026-07-07 16:37:30 UTC"
  }
}

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Chat not found or not accessible
1683 (Resource Missing)404Message (turn) not found in the chat
1680 (Access Denied)401You do not have permission to access this thread
1654 (Internal Error)500Genuine internal/datastore failure

Stream message response (SSE)

GET /current/workspace/{workspace_id}/ai/agent/{chat_id}/message/{message_id}/read/

Returns a Server-Sent Events (SSE) stream of the AI response.

Auth: Bearer token required. content_ai plan feature required.

Request example:

curl -N -X GET "https://api.fast.io/current/workspace/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/read/" \
  -H "Authorization: Bearer {jwt_token}" \
  -H "Accept: text/event-stream"

SSE stream format:

event: data
data: {"item": "The quarterly report "}

event: data
data: {"item": "shows revenue growth of "}

event: data
data: {"item": "15% year over year."}

event: analysis_data
data: {"type": "analysis_chunk", ...}

event: table_data
data: {"type": "table_data", ...}

event: done

SSE event types:

Event TypeDescription
dataText chunks of the AI response. Payload: {"item": "..."}. Concatenate all data events for the full text.
eventStatus update or event notification
analysis_dataStructured analysis data, citations, and references to source files
commentaryInterim narration the AI emits while working. Payload: {"text": "...", "parts": [...]}text is the flattened narration, parts the ordered content parts (text and inline object references). Expect one per work step on multi-step responses.
statusCosmetic turn-progress hint emitted before the agent's first output frame. Payload: {"phase": "...", "text": "..."}phase is enhancing (first turn only, while the question is enhanced/evaluated; clients commonly show "Analyzing your request…") or invoking_agent (every turn, just before the agent runs; clients commonly show "Connecting agent…"), text a human-readable default you may show or override. Not persisted and not replayed from the durable record — treat as a best-effort indicator and clear it once real output (or a terminal event) arrives. Unknown phases → generic "working".
table_dataTabular data extracted or generated by the AI
needs_inputTerminal event: the assistant needs more information and returned a single clarifying question instead of a full response. The question text arrives on a preceding data frame (payload includes a question field); fetch the message details to read it from the result's clarification object. The message reaches a needs_input terminal state (not failed) — present the question and send the user's answer as a new message in the same chat. Listen for it as its own event; the stream closes after it.
doneStream complete. No more events will be sent.

Behavior:

Error responses:

Error CodeHTTP StatusCause
1683 (Resource Missing)404Message (turn) not found in the chat
1609 (Not Found)404Chat not found or not accessible
1680 (Access Denied)401You do not have permission to read this thread
1654 (Internal Error)500Genuine internal/datastore failure

Publish a private chat

POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/publish/

Makes a private chat public (visible to other workspace members). One-way operation — published chats cannot be made private again.

Currently disabled (platform-wide). Publishing a chat publicly is turned off for all accounts: this endpoint returns 403 Forbidden with message "Publishing chats publicly is currently disabled.", and creating a chat with privacy=public is refused the same way. Clients can detect availability via the capabilities.can_publish_agent_chat boolean on workspace details (currently false) and hide the publish control. Chats already published before this change remain public.

Auth: Bearer token required. content_ai plan feature required.

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat not found or locked
1660 (Conflict)409Chat is already public
1664 (Datastore Error)500Update failed

Generate AI Share

POST /current/workspace/{workspace_id}/ai/share/

Generates markdown with temporary download URLs for selected files. Designed to be pasted into external AI chatbots.

Auth: Bearer token required. Workspace view permission. Does NOT require content_ai plan feature — available on all plans.

ParameterTypeRequiredDescription
filesarray (JSON)YesJSON array of file opaque IDs. Min 1, max 25.

Request example:

curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/ai/share/" \
  -H "Authorization: Bearer {jwt_token}" \
  --data-urlencode 'files=["aBcDeFgHiJkLmN", "oPqRsTuVwXyZ12"]'

The endpoint reads form-encoded input (application/x-www-form-urlencoded). The files field value must be a JSON-encoded array of node opaque IDs. Do NOT send a JSON request body (Content-Type: application/json) — only form-encoded bodies are parsed.

Response (200 OK):

{
  "result": true,
  "markdown": "## Files\n\n### quarterly-report.pdf\n[Download](https://api.fast.io/...)\nSize: 2.5 MB\n\n..."
}
FieldTypeDescription
response.markdownstringGenerated markdown with file info and temporary download URLs

Notes:

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Empty files array
1605 (Invalid Input)406More than 25 files

List AI transactions

GET /current/workspace/{workspace_id}/ai/transactions/

Returns up to 40 most recent AI token usage transactions for the workspace. Workspace-only — no share equivalent.

Results merge two sources into one most-recent-first feed: standalone AI operations (file summaries, title generation, indexing, and other one-off AI tasks) and completed agent conversation turns. Agent-turn entries carry type agent; standalone operations carry their operation type (e.g. chat_with_files, generate_title). In-progress turns are not included — only finished work appears.

Auth: Bearer token required. Workspace view permission. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/ai/transactions/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "count": 2,
  "items": [
    {
      "id": "txn_abc123",
      "type": "chat_with_files",
      "status": "complete",
      "tokens": 1500,
      "updated": "2025-06-15 10:30:05 UTC",
      "created": "2025-06-15 10:30:00 UTC"
    }
  ]
}
FieldTypeDescription
response.countintegerNumber of transactions returned
response.items[].idstringFormatted transaction or turn ID
response.items[].typestringOperation type (e.g., chat_with_files, generate_title) or agent for a completed agent conversation turn
response.items[].statusstringTransaction or turn status (e.g., complete, failed, cancelled, needs_input). needs_input marks a turn the assistant answered with a clarifying question instead of a full response.
response.items[].tokensintegerToken credits consumed
response.items[].updatedstringLast update timestamp (YYYY-MM-DD HH:MM:SS UTC)
response.items[].createdstringCreation timestamp (YYYY-MM-DD HH:MM:SS UTC)

Asking a Question and Getting the Response

Complete workflow

1. Create the chat:

curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/ai/agent/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "question=What were the Q3 revenue figures?"

The response includes thread.thread_id (the chat id) and turn.turn_id (the first message id). The AI begins processing asynchronously.

2. Wait for completion using activity polling (do NOT poll the message endpoint in a loop):

curl -X GET "https://api.fast.io/current/activity/poll/{workspace_id}?wait=95&lastactivity={timestamp}" \
  -H "Authorization: Bearer {jwt_token}"

Watch for the ai_chat:{chatId} activity key. This fires when the message state changes. The server holds the connection for up to 95 seconds and returns immediately when something changes.

3. Check message state:

curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/ai/agent/{chat_id}/message/{message_id}/details/" \
  -H "Authorization: Bearer {jwt_token}"

Turn states: pendingrunningcomplete (or the terminal failed, cancelled, lost, or needs_input). Only read the answer when the status is complete (handle needs_input as a clarifying question).

4. Stream the response:

curl -N -X GET "https://api.fast.io/current/workspace/{workspace_id}/ai/agent/{chat_id}/message/{message_id}/read/" \
  -H "Authorization: Bearer {jwt_token}" \
  -H "Accept: text/event-stream"

Returns SSE with event types: data (text chunks), commentary (interim narration), status (turn-progress hints, before the agent’s first frame), analysis_data, table_data, needs_input (terminal: a single clarifying question instead of a full answer — present it and send the user’s reply as a new message), done.

5. Send follow-ups:

curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/ai/agent/{chat_id}/message/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "question=How does that compare to Q2?"

Same polling flow for the reply.

Linking a user to an AI chat

Construct a workspace URL with a chat query parameter:

https://{org_domain}.fast.io/workspace/{workspace_name}?chat={chat_opaque_id}

The chat_opaque_id is the thread’s opaque id — returned as thread.thread_id when creating a chat, and as each item’s thread_id when listing chats.

Share AI Endpoints

Share AI endpoints follow the same pattern as workspace AI. Replace /workspace/{workspace_id} with /share/{share_id}.

Auto-generate OG image

GET /current/share/{share_id}/ai/autoog/

Generates an Open Graph image for the share. Returns binary PNG image data (not JSON).

Auth: Conditional. Public shares (Anyone / RegisteredUsers) need no authentication and get a custom AI-generated image. Private shares require Bearer auth and the ai_autoog plan feature; without valid permission or the feature, the endpoint falls back to the default private OG image (still HTTP 200 image data).

BehaviorDescription
Public shareCustom AI-generated image based on share content
Private shareCustom image when the caller has permission and the ai_autoog feature; otherwise the default private image

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Share is disabled
1654 (Internal Error)500Default image not found on server

Auto-generate title and description

POST /current/share/{share_id}/ai/autotitle/

AI-generates a title, description, and display type based on the share’s contents. Values are applied directly to the share.

Auth: Bearer token required. Requires the ai_autotitle plan feature (not content_ai/ai_agent) — a plan without it is rejected.

ParameterTypeRequiredDescription
user_contextstringNoOptional user-provided context to guide AI generation

Request example:

curl -X POST "https://api.fast.io/current/share/1234567890123456789/ai/autotitle/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "title": "Q4 Financial Reports",
  "description": "Quarterly financial reports and analysis for fiscal year 2025.",
  "display_type": "document"
}
FieldTypeDescription
response.titlestringAI-generated title
response.descriptionstringAI-generated description
response.display_typestringAI-suggested display type

Error responses:

Error CodeHTTP StatusCause
1680 (Access Denied)401Insufficient share permissions
1654 (Internal Error)500Share update or generation failure

Share chat endpoints mirror workspace chat endpoints. All response formats and schemas are identical to the workspace versions documented above. The key differences are:

Create a new chat (Share)

POST /current/share/{share_id}/ai/agent/

Creates a chat with an initial message in a share. The AI begins processing asynchronously.

Auth: Bearer token required. Share view permission and chat permission. content_ai and ai_agent plan features required.

ParameterTypeRequiredDefaultDescription
questionstringYesInitial question, 1–32,000 characters. (May be omitted only when content_parts carries the message text.)
namestringNoAuto-generatedChat name. A default is used if omitted.
kindstringNouserShare-context creation only produces user chats.
referencesJSON arrayNoFile/folder reference items to attach as context — each a {type, id} file or folder item (see Attaching Files and Folders). Up to 20 files / 200 MB / 100 references; the backend resolves each item’s full details server-side.
content_partsJSON arrayNoOrdered content stream — text segments plus inline file/folder reference pills (same item shape as references).
subjectsJSON arrayNoFile/folder reference items pinned as focus subjects for the turn (same item shape as references).
uploadsJSON arrayNoFocus files staged from outside the share.

Share-context chats are always private — the privacy parameter is not accepted and visibility is fixed so guests do not see each other's AI conversations. Also accepts the optional view, activity, role_in_org, and idempotency_key fields.

Request example:

curl -X POST "https://api.fast.io/current/share/1234567890123456789/ai/agent/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "question=What were the Q3 revenue figures?"

Response (200 OK):

Same { thread, turn } shape as the workspace create endpoint. thread.thread_id is the chat id; turn.turn_id is the first message id. See Create a new chat (workspace) above for the full field lists.

{
  "result": true,
  "thread": { "thread_id": "aBcDeFgHiJkLmNoPqR", "scope": { "type": "share", "id": "1234567890123456789" }, "status": "active", "kind": "user", "...": "..." },
  "turn": { "turn_id": "xYzAbCdEfGhIjKlMnO", "thread_id": "aBcDeFgHiJkLmNoPqR", "seq": 1, "status": "pending", "...": "..." }
}

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Invalid kind or name, or invalid question length
1609 (Not Found)404An attached file or folder reference does not exist or is not accessible
1605 (Invalid Input)406An attached reference is malformed, the wrong node type, or exceeds the 20-file / 200 MB / 100-reference limit
1680 (Access Denied)401Folder attachment is not permitted in this share (restricted-view guest)
1660 (Conflict)409Thread still committing its first turn (retry), or the first message is too large
1664 (Datastore Error)500Thread or turn creation failed

List chats (Share)

GET /current/share/{share_id}/ai/agent/list/

Returns all chats created by the current user in the share. Sorted by most recently modified first.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Query parameters:

ParameterTypeRequiredDefaultDescription
kindstringNouserFilter by chat kind. Allowed values: user (default), agent, all. Same semantics as the workspace list endpoint. Note: share-context chat creation does not accept kind, so all share-created chats are user.

Variant: Append /deleted to the path to list deleted chats: GET .../ai/agent/list/deleted

Request example:

curl -X GET "https://api.fast.io/current/share/1234567890123456789/ai/agent/list/" \
  -H "Authorization: Bearer {jwt_token}"

Response: Same format as workspace chat list. See List chats above.

Get chat details (Share)

GET /current/share/{share_id}/ai/agent/{chat_id}/details/

Returns chat details with full message history.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/details/" \
  -H "Authorization: Bearer {jwt_token}"

Response: Same format as workspace chat details. See Get chat details above.

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Chat not found or not accessible
1680 (Access Denied)401You do not have permission to access this thread

Update a chat (Share)

POST /current/share/{share_id}/ai/agent/{chat_id}/update/

Update the name of an existing chat in a share.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

ParameterTypeRequiredDescription
namestringYesNew chat name

Request example:

curl -X POST "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/update/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "name=Updated Chat Name"

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat not found or locked
1605 (Invalid Input)406Invalid name value
1664 (Datastore Error)500Update failed

Delete a chat (Share)

DELETE /current/share/{share_id}/ai/agent/{chat_id}/

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Request example:

curl -X DELETE "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat not found or locked
1654 (Internal Error)500Chat in non-deletable state or internal error

Deleted chats can be listed via GET .../ai/agent/list/deleted.

Send a follow-up message (Share)

POST /current/share/{share_id}/ai/agent/{chat_id}/message/

Send a new message to an existing chat in a share. The message is processed asynchronously.

Auth: Bearer token required. Share view permission and chat permission. content_ai and ai_agent plan features required.

ParameterTypeRequiredDefaultDescription
questionstringYesFollow-up question, 1–32,000 characters. (May be omitted only when content_parts carries the message text.)
referencesJSON arrayNoFile/folder reference items to attach as context — each a {type, id} file or folder item (see Attaching Files and Folders). Up to 20 files / 200 MB / 100 references; the backend resolves each item’s full details server-side.
content_partsJSON arrayNoOrdered content stream — text segments plus inline file/folder reference pills (same item shape as references).
subjectsJSON arrayNoFile/folder reference items pinned as focus subjects for the turn (same item shape as references).
uploadsJSON arrayNoFocus files staged from outside the workspace/share.

Also accepts the optional view, activity, role_in_org, and idempotency_key fields (see Advanced Per-Turn Fields above). There is no type parameter — the turn is appended to the existing thread.

Request example:

curl -X POST "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/message/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d "question=How does that compare to Q2?"

Response (200 OK):

{
  "result": true,
  "turn_id": "mNoPqRsTuVwXyZaBcD",
  "thread_id": "aBcDeFgHiJkLmNoPqR",
  "seq": 2,
  "status": "pending",
  "idempotency_key": "7c1b…",
  "query": { "text": "How does that compare to Q2?" },
  "error": null,
  "cost": { "credits": 0, "tokens": 0 },
  "created_at": "2026-07-07 16:37:29 UTC",
  "updated_at": "2026-07-07 16:37:29 UTC"
}

The created turn’s fields are returned at the top level (not nested under a message object). The turn_id is the message id you poll or stream. status starts at pending; watch it reach a terminal state (see “Get message details”).

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Thread not found, not accessible, or locked
1680 (Access Denied)401You cannot message this thread; or (share chats only) folder attachment is not permitted for a restricted-view guest
1609 (Not Found)404An attached file or folder reference does not exist or is not accessible
1605 (Invalid Input)406An attached reference is malformed, the wrong node type, or exceeds the 20-file / 200 MB / 100-reference limit
1660 (Conflict)409The conversation has grown too large to continue — start a new chat
1664 (Datastore Error)500Transient storage error loading an attached file (retryable)
1654 (Internal Error)500Message creation or queuing failed

Cancel an in-progress message (Share)

POST /current/share/{share_id}/ai/agent/{chat_id}/cancel/

Aborts an in-flight AI message in a share chat. Behavior matches the workspace cancel endpoint above: the worker halts streaming and the affected turn reaches a cancelled terminal state, after which a new message can be sent.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required (the cancel endpoint does not require ai_agent).

Body: Empty.

Request example:

curl -X POST "https://api.fast.io/current/share/{share_id}/ai/agent/{chat_id}/cancel/" \
  -H "Authorization: Bearer {jwt_token}"

Response: { "result": true } — same as the workspace cancel endpoint (no success, message.id, or no_pending_message field). See Cancel an in-progress message above for the full behavior notes (idempotency, best-effort latency, partial billing, SSE cancelled event, and the affected turn reaching the cancelled terminal state).

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat could not be loaded or the cancel signal could not be issued

List messages in a chat (Share)

GET /current/share/{share_id}/ai/agent/{chat_id}/messages/list/

Returns all messages in chronological order (oldest first).

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/messages/list/" \
  -H "Authorization: Bearer {jwt_token}"

Response: Same format as workspace message list. See List messages in a chat above.

Get message details (Share)

GET /current/share/{share_id}/ai/agent/{chat_id}/message/{message_id}/details/

Retrieve detailed information about a specific message, including response text, citations, and cost.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Request example:

curl -X GET "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/details/" \
  -H "Authorization: Bearer {jwt_token}"

Response: Same format as workspace message details. See Get message details above.

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Chat not found or not accessible
1683 (Resource Missing)404Message (turn) not found in the chat
1680 (Access Denied)401You do not have permission to access this thread
1654 (Internal Error)500Genuine internal/datastore failure

Stream message response (SSE) (Share)

GET /current/share/{share_id}/ai/agent/{chat_id}/message/{message_id}/read/

Returns a Server-Sent Events (SSE) stream of the AI response.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Request example:

curl -N -X GET "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/read/" \
  -H "Authorization: Bearer {jwt_token}" \
  -H "Accept: text/event-stream"

SSE stream format and behavior: Identical to the workspace version. See Stream message response (SSE) above.

Error responses:

Error CodeHTTP StatusCause
1683 (Resource Missing)404Message (turn) not found in the chat
1609 (Not Found)404Chat not found or not accessible
1680 (Access Denied)401You do not have permission to read this thread
1654 (Internal Error)500Genuine internal/datastore failure

Publish a private chat (Share)

POST /current/share/{share_id}/ai/agent/{chat_id}/publish/

Makes a private chat public (visible to other share members). One-way operation — published chats cannot be made private again.

Currently disabled (platform-wide). Publishing a chat publicly is turned off for all accounts: this endpoint returns 403 Forbidden with message "Publishing chats publicly is currently disabled." Clients can detect availability via the capabilities.can_publish_agent_chat boolean on share details (currently false) and hide the publish control. Chats already published before this change remain public.

Auth: Bearer token required. Share view permission and chat permission. content_ai plan feature required.

Request example:

curl -X POST "https://api.fast.io/current/share/1234567890123456789/ai/agent/aBcDeFgHiJkLmNoPqR/publish/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1658 (Not Acceptable)406Chat not found or locked
1660 (Conflict)409Chat is already public
1664 (Datastore Error)500Update failed

Generate AI Share (Share)

POST /current/share/{share_id}/ai/share/

Generates markdown with temporary download URLs for selected files. Designed to be pasted into external AI chatbots.

Auth: Bearer token required. Share view permission and download permission. Does NOT require content_ai plan feature — available on all plans.

ParameterTypeRequiredDescription
filesarray (JSON)YesJSON array of file opaque IDs. Min 1, max 25.

Request example:

curl -X POST "https://api.fast.io/current/share/1234567890123456789/ai/share/" \
  -H "Authorization: Bearer {jwt_token}" \
  --data-urlencode 'files=["aBcDeFgHiJkLmN", "oPqRsTuVwXyZ12"]'

The endpoint reads form-encoded input (application/x-www-form-urlencoded). The files field value must be a JSON-encoded array of node opaque IDs. Do NOT send a JSON request body (Content-Type: application/json) — only form-encoded bodies are parsed.

Response (200 OK):

{
  "result": true,
  "markdown": "## Files\n\n### quarterly-report.pdf\n[Download](https://api.fast.io/...)\nSize: 2.5 MB\n\n..."
}
FieldTypeDescription
response.markdownstringGenerated markdown with file info and temporary download URLs

Notes:

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Empty files array
1605 (Invalid Input)406More than 25 files
1680 (Access Denied)401Insufficient download permissions

Workspace AI vs. Share AI differences

FeatureWorkspace AIShare AI
AI Transactions endpointYesNo
Auto OG image endpointNoYes
Auto title endpointNoYes
content_ai feature requiredYes (except AI Share)Yes (except AI Share)
ai_agent feature requiredYes, for create-chat and send-message (except AI Share)Yes, for create-chat and send-message (except AI Share)
File scope contextWorkspace filesShare files

AI Share File Download

GET /current/ai/share/{token}?file={index}

Download a file from an AI Share using a temporary token. No authentication required — access is controlled by the token.

ParameterTypeRequiredDescription
{token}string (path)YesAlphanumeric AI share token (generated by the AI Share creation endpoint)
fileinteger (query)YesZero-based file index within the AI Share

Request example:

curl -X GET "https://api.fast.io/current/ai/share/aBcDeFgHiJkLmNoPqRsTuVwXyZ?file=0" \
  -o downloaded_file.pdf

Success response: Binary file data with appropriate Content-Type, Content-Disposition, Content-Length, and Accept-Ranges headers. Supports HTTP range requests.

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Token missing, invalid, expired, or use limit reached
1609 (Not Found)404File index out of range
1654 (Internal Error)500Unable to retrieve or read file

All invalid/expired token errors return 404 to prevent token enumeration.

Deprecated: The /ai/search/ endpoint is deprecated. Use GET /current/workspace/{id}/storage/search/ (or /share/{id}/storage/search/) instead. The unified storage search endpoint automatically performs semantic search when workspace intelligence is enabled. Pass the search parameter for your query, and optionally files_scope and folders_scope to narrow results. Semantic results include relevance_score, content_snippet, match_source, media_segment, mimetype, and search_metadata fields.

The legacy endpoints below continue to work but will be removed in a future release.

GET /current/workspace/{workspace_id}/ai/search/

Auth: Bearer token required. Workspace view permission. content_ai plan feature required.

GET /current/share/{share_id}/ai/search/

Identical to workspace semantic search but scoped to a share.

ParameterTypeRequiredDefaultDescription
questionstringYesSearch query, 2–1,000 characters. (The parameter is named question, not query_text.)
files_scopestringNoAll indexed filesComma-separated nodeId:versionId pairs (max 100)
folders_scopestringNoAll indexed filesComma-separated nodeId:depth pairs (max 100, depth 1–10)
limitintegerNo20Results per page, 1–500
offsetintegerNo0Pagination offset

This deprecated endpoint has no details parameter — node enrichment via details=true is a feature of /storage/search (below), not this endpoint. It also returns 1605 (Invalid Input) / 406 when workspace intelligence is not enabled.

Preferred approach — use /storage/search instead:

# Basic search
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/search/?search=quarterly%20revenue&limit=10" \
  -H "Authorization: Bearer {jwt_token}"

# Search with full node details
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/search/?search=quarterly%20revenue&details=true" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "response_code": 200,
  "results": [
    {
      "content": "The quarterly revenue showed a 15% increase...",
      "score": 0.95,
      "node": {
        "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
        "type": "file",
        "name": "quarterly-report.pdf",
        "parent": "root",
        "size": 1048576,
        "mimetype": "application/pdf",
        "ai": { "state": "indexed", "attach": true, "summary": true }
      }
    }
  ],
  "pagination": {
    "total": 25,
    "limit": 10,
    "offset": 0,
    "has_more": true
  }
}
FieldTypeDescription
results[].contentstringMatched text snippet from the indexed document
results[].scorefloatRelevance score (0.0–1.0, higher is more relevant)
results[].nodeobject/nullFull node resource, or null if the file was deleted
results[].file_detailsobjectRaw metadata when node is null (node_id, version_id, name, mimetype)
pagination.totalintegerTotal number of results
pagination.has_morebooleanWhether more results are available

Hybrid response fields (intelligence enabled):

When workspace intelligence is enabled, each file entry in the /storage/search response includes additional semantic fields:

FieldTypeDescription
relevance_scorefloatSemantic relevance score (0.0–1.0)
content_snippetstring/nullThe actual matching text from semantic search. NULL for keyword-only matches. Trimmed by the output query param on /storage/search/ (terse ~200 bytes, standard ~600 bytes, full untrimmed; truncated values end with …).
match_sourcestringSource of the match: keyword, semantic, or both
mimetypestringFile MIME type (e.g., application/pdf, audio/mpeg). Present for semantic matches.
media_segmentobjectOnly for audio/video matches when intelligence is on. Contains start_seconds and end_seconds for deep-linking to the exact timestamp range.
search_metadataobjectAdditional search metadata

Response with details=true (via /storage/search):

When using the preferred /storage/search endpoint with ?details=true, each file entry includes a node field with the full node resource:

{
  "result": true,
  "files": {
    "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf": {
      "name": "report.pdf",
      "parent_id": "...",
      "type": "file",
      "relevance_score": 0.92,
      "content_snippet": "Revenue increased 15%...",
      "match_source": "both",
      "mimetype": "application/pdf",
      "node": {
        "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
        "name": "report.pdf",
        "type": "file",
        "size": 123456,
        "previews": { "...": "..." },
        "ai": { "state": "indexed" }
      }
    }
  }
}

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Intelligence not enabled
1605 (Invalid Input)406Malformed files_scope or folders_scope

How to Phrase Questions

With folder/file scope (RAG)

Write questions that will match content in indexed files. The AI searches for relevant passages and cites them. Be specific.

With file attachments

You can be more direct since the AI has the full file content.

Chat Session Object Schema

The thread (chat) resource is returned as thread by the details endpoint and as each chats.items[] entry by the list endpoint. The details endpoint returns the message history separately as a turns collection — there is no embedded messages array on the thread.

FieldTypeDescription
thread_idstringOpaque ID of the thread (the chat)
creatorobject{type: string, id: string} — the chat creator
scopeobject{type: string, id: string} — the workspace or share the chat lives in
namestringDisplay name of the chat
statusstringCurrent chat status
kindstringuser or agent — set at creation, immutable thereafter
costobject{credits: int, tokens: int} — credit charge and the raw token count it derives from
privacyobject{visibility: "private"|"public", owner: {type, id}|null}
created_atstringCreation timestamp (YYYY-MM-DD HH:MM:SS UTC)
updated_atstringLast update timestamp (YYYY-MM-DD HH:MM:SS UTC)
message_countintegerTotal turns (read endpoints only; omitted on mutation acks)
continuablebooleanWhether the chat can be continued (read endpoints only)
latest_messageobject/nullMost recent turn preview (list endpoint only)

There is no type, unique_creators, or efficiency field.

Message Object Schema

A message is a turn. The lightweight shape (list/details/mutation responses) carries the fields above the divider; the per-turn detail endpoint additionally returns result and actions.

FieldTypeDescription
turn_idstringOpaque ID of the turn (the message)
thread_idstringParent thread (chat) opaque ID
seqintegerTurn sequence number within the thread
statusstringpending, running, complete, failed, cancelled, lost, or needs_input
idempotency_keystringPer-turn idempotency key
queryobjectThe user’s submitted question: { text, content_parts?, references?, uploads?, subjects? }
errorobject/null{ message: string, grpc_status: int|null } on a failed/lost turn; null otherwise
costobject{ credits: int, tokens: int } — the turn’s credit charge and raw token count
created_atstringCreation timestamp (YYYY-MM-DD HH:MM:SS UTC)
updated_atstringLast update timestamp (YYYY-MM-DD HH:MM:SS UTC)
resultobject/nullDetail view only. Decompressed answer blob: answer, references, citations, thought_transcript, commentary_transcript, thought_events, commentary_events, and (for needs_input) clarification. null until the turn is terminal
actionsarrayDetail view only. Ordered, replayable action cards (seq, label, state, affected_refs, started_at, ended_at)

There is no state, personality, response, author_name, or top-level text/citations/events field — the answer and its citations live inside the detail view’s result blob, and the processing state is status.

Citation Format

Citations appear inside a completed turn’s result blob (under result.citations / result.references), fetched from Get message details. They reference specific locations in files that informed the AI response.

FieldTypeDescription
hashstringFile content hash (used for grouping)
nodeIdstringStorage node opaque ID
versionIdstringFile version opaque ID
entriesarrayCitation locations within the file
entries[].pageintegerPage number in the document
entries[].snippetstring/nullRelevant text excerpt
entries[].timestampfloat/nullTimestamp for audio/video files (seconds)

Activity Polling for AI Chat Completion

Do NOT poll the message details endpoint in a loop. Use activity long-polling instead.

GET /current/activity/poll/{workspace_id}?wait=95&lastactivity={timestamp}

The server holds the connection for up to 95 seconds and returns immediately when something changes. Watch for the ai_chat:{chatId} activity key — this fires when the message state changes.

Activity Key PatternWhat Changed
ai_chat:{chatId}AI chat message state updated
storage:{fileId}File added, updated, or removed
preview:{fileId}File preview/thumbnail is ready

Pass the returned lastactivity timestamp into your next poll to receive only newer changes.

Anti-pattern: Do not GET .../ai/agent/{id}/message/{id}/details/ in a loop. Poll once on the workspace activity endpoint and wait for the ai_chat key.

Metadata Templates

Structured metadata for workspace files. Templates define schemas (fields, types, constraints) and have many-to-many relationships with files — a template can be applied to multiple files, and files can have metadata from multiple templates. Templates are managed at the workspace level.

The metadata feature is available on all plan tiers, with three distinct caps that scale by plan:

PlanMax TemplatesMax Files per TemplateMax Fields (Columns) per Template
Starter2100010
Business10100050
Growth10100050

Listing, details, and preview endpoints return plan_node_limit, is_truncated, and an unfiltered count alongside the visible (capped) count, so consumers can render upsell messaging. Manual /nodes/add rejects requests that would exceed the per-template file cap. Auto-match silently truncates its candidate set to the remaining slots and incurs no LLM cost when the template is already at its cap. On downgrade, overflow rows are preserved but hidden; under truncation, the visible window is ordered by mapping creation order (oldest-mapped first), so the same files remain visible across plan changes and re-upgrade restores full visibility.

The field cap bounds the number of fields (columns) declared in a template's schema. It is enforced at every commit point that mutates the field set: create, update, and copy. Exceeding the cap returns 1605 (Invalid Input) with a message describing the attempted field count and the cap. The /details and /list endpoints surface the current declared field count as field_count and the per-plan ceiling as plan_field_limit (-1 when unlimited or unresolved).

Each template field also carries an autoextract boolean (defaults to true). Fields with autoextract=false are excluded from the default auto-extraction scope, which is useful for purely manual fields. Every template must have at least one field with autoextract=true; templates that opt every field out are rejected at create/update/copy. A full-row extract request that would yield an empty effective scope short-circuits with success and does not enqueue a job; an explicit fields array on the extract endpoint overrides the flag and is always honored.

Per-node autoextract eligibility is surfaced as an autoextractable boolean on both the metadata details endpoint (GET /workspace/{id}/storage/{node_id}/metadata/details/) and the template nodes listing (GET /workspace/{id}/metadata/templates/{tid}/nodes). It is true when the node is a non-trashed file with a completed AI summary — the same signal the extraction pipeline uses — and clients can use it to gate "extract now" affordances without making an extra probe.

Files are linked to templates either manually (add/remove endpoints) or automatically via AI-based matching. When intelligence is enabled, metadata is automatically extracted during file ingestion for documents, spreadsheets, images (PNG, JPEG, WebP), and code files.

Compact Responses (output=)

Every metadata endpoint that returns object-metadata, template, saved-view, or eligible-node records accepts an optional output query parameter that selects the shape of each record in the response. A single detail-level token may be combined with modifier tokens; specifying two detail levels (e.g. ?output=terse,standard) returns HTTP 406. When output= is omitted, responses are full and byte-for-byte unchanged.

Object metadata (GET /workspace/{id}/storage/{node}/metadata/details/):

LevelFields returned on each metadata record (cumulative)
terseobject_id, template_id, node_id (narrowed to {id, name, type}), metadata (the full key/value payload)
standardterse + instance_id, node_id widened to {id, name, type, parent, mimetype}, autoextractable
fulleverything

The key/value payload is the point of a metadata response, so terse keeps it. The savings come from trimming the nested node pointer — terse carries only the minimum identity fields needed to address the node in a follow-up call, standard adds parent and mimetype for list rendering, and full keeps the entire node resource.

Templates (GET /workspace/{id}/metadata/templates/list/, GET /workspace/{id}/metadata/templates/{tid}/details/):

LevelFields returned (cumulative)
terseid, name, description, enabled, priority
standardterse + instanceId, orgId, locked, deleted, updated, created, fields, node_count, field_count, node_count_capped, plan_node_limit, is_truncated, plan_field_limit
fulleverything

deleted is included in standard so trash/restore workflows can surface the soft-delete timestamp without promoting to full.

Saved views (GET /workspace/{id}/metadata/views/):

LevelFields returned (cumulative)
tersename
standardterse + node_id, filters, order_by, order_desc, created, updated
fulleverything

Eligible nodes (GET /workspace/{id}/metadata/eligible/):

LevelFields returned (cumulative)
tersenode_id, name, mimetype
standardterse + size, summary_title, summary_short, updated, templates
fulleverything

Unknown tokens are silently ignored. Add the markdown modifier (e.g. ?output=standard,markdown) to receive the response as GitHub-flavored Markdown (Content-Type: text/markdown; charset=UTF-8) instead of JSON — see the cross-cutting ?output= reference for the full contract.

Create a template

POST /current/workspace/{workspace_id}/metadata/templates/

Create a new metadata template defining fields, types, and constraints.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
namestringYesTemplate name (1–100 characters)
descriptionstringYesTemplate description (max 255 characters)
fieldsstring (JSON)YesJSON-encoded array of field definitions

There is no category parameter — the create endpoint accepts only name, description, and fields.

Field definition structure:

PropertyTypeDescription
namestringField identifier (alphanumeric + underscore)
descriptionstringHuman-readable description
typestringstring, int, float, bool, json, url, or datetime
minnumberMinimum value/length constraint
maxnumberMaximum value/length constraint
defaultmixedDefault value
fixed_listarrayAllowed values for dropdown-style fields
can_be_nullbooleanWhether the field allows null values

Request example:

curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/metadata/templates/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d 'name=Invoice+Template' \
  -d 'description=Metadata+schema+for+invoices' \
  -d 'fields=[{"name":"invoice_number","description":"Invoice number","type":"string","min":1,"max":50,"can_be_null":false},{"name":"amount","description":"Total amount","type":"float","min":0,"can_be_null":false}]'

Response (200 OK):

{
  "result": true,
  "template": {
    "id": "mt_aBcDeFgHiJkLmN",
    "instanceId": "1234567890123456789",
    "orgId": "9876543210987654321",
    "name": "Invoice Template",
    "description": "Metadata schema for invoices",
    "locked": false,
    "priority": null,
    "enabled": true,
    "deleted": null,
    "updated": "2025-01-20 10:30:00 UTC",
    "created": "2025-01-20 10:30:00 UTC",
    "fields": [
      {
        "name": "invoice_number",
        "description": "Invoice number",
        "type": "string",
        "min": 1,
        "max": 50,
        "fixed_list": [],
        "can_be_null": false
      }
    ]
  }
}

Newly created templates are active immediately — enabled is true for every template in current responses (there is no per-workspace enable/disable toggle).

Delete a template

DELETE /current/workspace/{workspace_id}/metadata/templates/{template_id}/

Soft-delete a metadata template. The delete is idempotent: deleting a template that no longer exists returns success. A template owned by a different workspace or organization (including system templates) is not visible to this endpoint and returns 404.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Error responses:

Error CodeHTTP StatusCause
1609 (Not Found)404Template belongs to a different workspace or organization
1664 (Datastore Error)500Deletion failed

List templates

GET /current/workspace/{workspace_id}/metadata/templates/list/

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Optional path filter (append to URL): all (default, both system and custom), custom (non-system only), system (system only), enabled, or disabled.

Request example:

# List all templates
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/metadata/templates/list/" \
  -H "Authorization: Bearer {jwt_token}"

# List only enabled templates
curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/metadata/templates/list/enabled/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "count": 2,
  "items": [
    {
      "id": "mt_aBcDeFgHiJkLmN",
      "instanceId": "1234567890123456789",
      "orgId": "9876543210987654321",
      "name": "Contract Template",
      "description": "Standard contract metadata fields",
      "locked": true,
      "priority": 3,
      "enabled": true,
      "deleted": null,
      "updated": "2025-01-15 08:00:00 UTC",
      "created": "2025-01-01 00:00:00 UTC",
      "fields": [
        {
          "name": "contract_type",
          "description": "Type of contract",
          "type": "string",
          "fixed_list": ["NDA", "MSA", "SOW"],
          "can_be_null": false
        }
      ]
    }
  ]
}

Get template details

GET /current/workspace/{workspace_id}/metadata/templates/{template_id}/details/

Returns the full template with fields and workspace-level instance settings.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Update template definition

POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/update/

Update a template’s field definitions. All body fields are optional — only provided fields are updated.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
namestringNoUpdated template name (1–100 characters)
descriptionstringNoUpdated description (1–255 characters)
fieldsstring (JSON)NoUpdated JSON-encoded array of field definitions

There is no category parameter.

Append /create/ to the path to copy the template instead of updating in place.

Response: Returns the updated template object. When the fields array changes in a way that invalidates stored values, the response additionally includes a schema_update object. Auto-triggered partial re-extraction runs only for: (a) new field names, and (b) type changes on existing names. Soft edits (description, min/max, nullable, fixed_list, regex) bump the schema hash but do not auto-trigger extraction — call the extract-all endpoint manually if you want those applied.

{
  "result": true,
  "template": { "...": "..." },
  "schema_update": {
    "previous_hash": 1234567890,
    "current_hash": 2345678901,
    "extraction_auto_enqueued": true,
    "added_fields": ["currency"],
    "type_changed_fields": ["total_amount"]
  }
}

The schema_update object is absent when only name or description change.

Template-Node Mapping

Templates have many-to-many relationships with files. Use these endpoints to manage which files are mapped to which templates.

Eligible nodes

GET /current/workspace/{workspace_id}/metadata/eligible/

Paginated list of files and notes eligible for metadata extraction (nodes that have both an AI summary and a preview ready). Folders and links are excluded.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
limitintegerNoNumber of items to return (1–500, default: 100)
offsetintegerNoNumber of items to skip (default: 0)

Add nodes to template

POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes/add/

Manually add nodes to a template. Both files and notes can be added; folders and links are rejected. Creates the many-to-many mapping between the template and the specified nodes.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
node_idsstring (JSON)YesJSON-encoded array of node IDs to add to the template

The number of files mapped to a single template is capped per plan tier. When the request would push the template above its cap, the endpoint returns 1605 (Invalid Input) with a message describing the cap, the attempted count, and the remaining slots. Concurrent calls cannot collectively exceed the cap.

Remove nodes from template

POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes/remove/

Remove nodes (files or notes) from a template. Removes the many-to-many mapping between the template and the specified nodes.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
node_idsstring (JSON)YesJSON-encoded array of node IDs to remove from the template

List nodes in template

GET /current/workspace/{workspace_id}/metadata/templates/{template_id}/nodes/

List nodes (files and notes) currently mapped to a template. The visible window is clamped to the workspace plan's per-template cap. When the template is truncated under the cap, rows are ordered by mapping creation order (oldest first) so the visible set is stable across plan changes. The default ordering when no sort_field is supplied is subject to change in upcoming releases; callers that depend on a specific ordering should pass sort_field explicitly.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
limitintegerNoNumber of items to return (1–500, default: 100)
offsetintegerNoNumber of items to skip (default: 0)
sort_fieldstringNoOptional template field name to sort by
sort_dirstringNoasc or desc (only with sort_field)

Response (additional fields):

FieldTypeDescription
total_countintegerVisible row count under the per-plan cap
total_count_unfilteredintegerTrue row count in storage (ignores cap)
plan_node_limitintegerPer-template node cap for the workspace's plan; -1 indicates unlimited / unresolved
is_truncatedbooleantrue when overflow rows exist beyond the cap

Pagination is clamped to the visible window: oversized limit values are silently shortened, and offsets past the visible window return an empty page. Existing pagination fields (items, count, next_cursor, has_more) are unchanged.

Preview template match

POST /current/workspace/{workspace_id}/metadata/templates/preview-match/

Synchronously preview which files would match a proposed metadata template before creating it. Accepts a template name and description, scans a sample of eligible files using AI classification, and returns the matched files with details. No template is created — this is a read-only preview operation.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
namestringYesProposed template name (1–255 characters)
descriptionstringYesProposed template description (1–2000 characters)

Response (200 OK):

{
  "result": true,
  "matched_files": [
    {
      "node_id": "aBcDeFgHiJkLmN",
      "name": "invoice_2025.pdf",
      "mimetype": "application/pdf",
      "summary_title": "Invoice from Acme Corp",
      "summary_short": "Invoice INV-2025-001 for $1,500.00"
    }
  ],
  "total_eligible": 45,
  "total_scanned": 45,
  "total_matched": 1,
  "plan_node_limit": 10,
  "would_truncate_at": 1
}
FieldTypeDescription
matched_filesarrayFiles that matched the proposed template
matched_files[].node_idstringFile node opaque ID
matched_files[].namestringFile name
matched_files[].mimetypestringMIME type
matched_files[].summary_titlestringAI-generated summary title
matched_files[].summary_shortstringAI-generated short summary
total_eligibleintegerSize of the eligible-file sample considered by this preview. When the workspace has more eligible files than fit in the sample window, this value is the sample ceiling (plus one, to signal "more available") rather than the true workspace-wide count
total_scannedintegerFiles actually scanned in this preview. May be smaller than total_eligible when the sample is bounded
total_matchedintegerFiles that matched
plan_node_limitintegerPer-template node cap for the workspace's plan; -1 indicates unlimited / unresolved
would_truncate_atintegerNumber of matches that would be persisted by a subsequent auto-match, given the current plan cap and the sampled matches. Equal to min(total_matched, plan_node_limit) when the cap applies, otherwise equal to total_matched. Because the preview only scans a sample, this is an estimate — a subsequent auto-match that scans the full workspace may match a different count. Frontends can show "X of Y matched — upgrade for more" when this value is less than total_matched.

Requires available AI credits. Only files with completed AI summaries and previews are eligible.

Suggest template fields

POST /current/workspace/{workspace_id}/metadata/templates/suggest-fields/

Synchronously suggest a set of custom metadata fields for a proposed template, using AI. Accepts a small sample of eligible file node IDs (typically drawn from a prior preview-match call), a template description, and optional user intent text. Returns 1–10 suggested field definitions shaped to be used directly as the fields parameter of the create-template endpoint. No template is created — this is a read-only suggestion operation. Sits between preview-match and create-template in a template-creation flow.

Auth: Bearer token required. Workspace member. Metadata billing feature required. Rate limited; concurrent calls per user+workspace return 409 Conflict.

ParameterTypeRequiredDescription
node_idsstring (JSON)YesJSON-encoded array of file node IDs to sample (1–25 items)
descriptionstringYesTemplate description (1–2000 characters)
user_contextstringNoOptional user intent string (max 64 characters, letters/numbers/spaces only)

Response (200 OK):

{
  "result": true,
  "suggested_fields": [
    {
      "name": "Location",
      "type": "string",
      "description": "Geographic location where the content was created",
      "max": 384,
      "can_be_null": true,
      "example_value": "New York, NY"
    },
    {
      "name": "Year",
      "type": "int",
      "description": "Year the document was created or published",
      "can_be_null": true,
      "example_value": 2024
    },
    {
      "name": "Priority",
      "type": "string",
      "description": "Priority level of the task",
      "max": 384,
      "can_be_null": false,
      "fixed_list": ["low", "medium", "high"],
      "example_value": "medium"
    }
  ],
  "file_sample_count": 25
}
FieldTypeDescription
suggested_fieldsarray1–10 suggested field definitions, directly compatible with create-template fields
suggested_fields[].namestringField display name
suggested_fields[].typestringOne of string, int, float, bool, datetime, url
suggested_fields[].descriptionstringHuman-readable field description
suggested_fields[].maxnumberMaximum length/value constraint (when applicable)
suggested_fields[].can_be_nullboolWhether the field may be null
suggested_fields[].fixed_listarrayAllowed values for bounded categorical fields (status, priority, category). Present only when the field is enumerable.
suggested_fields[].example_valuemixedDisplay-only hint showing what the extraction AI would likely return for this field, grounded in one of the sampled files. Type matches the field's type: string for string/url (HTML-stripped, max 256 chars), integer for int, number for float, boolean for bool, ISO 8601 string for datetime. May be omitted when no clean example is available.
file_sample_countintegerNumber of files actually used to generate suggestions

Requires available AI credits. The json field type is not returned by this endpoint. example_value is a preview hint only and is not part of the create-template field schema; clients should strip it before posting to create-template (passing it through is harmless as unknown keys are ignored).

Auto-match files to template

POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/auto-match/

Enqueues an async job that uses AI to match eligible files in the workspace to the template based on file content and template field definitions. The endpoint returns immediately with a job descriptor; results are written to the template asynchronously and progress can be polled via the jobs status endpoint under the template_match key.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Optional body parameters:

ParameterTypeDescription
batch_sizeintegerOptional batch size override; clamped server-side to the supported min/max range. Omit to use the default.

Response (200 OK) — new job enqueued:

{
  "result": true,
  "job_id": "aj_aBcDeFgHiJkLmN",
  "status": "queued",
  "message": "Template matching job enqueued"
}

Response (200 OK) — duplicate job already running:

{
  "result": true,
  "status": "duplicate",
  "message": "A template matching job is already in progress for this template"
}
FieldTypeDescription
job_idstringAsync job ID. Present when status is queued; absent when status is duplicate. Use the jobs status endpoint to poll progress under the template_match key.
statusstringqueued for a newly enqueued job, or duplicate when an in-flight job for the same template was detected and re-used.
messagestringHuman-readable status message.

The matched set is silently capped at the workspace's per-template node cap. Eligible files are truncated pre-batch (so no LLM credit is spent on candidates that cannot be persisted) and re-checked immediately before persistence to handle interleaved manual adds. When the template is already at its cap, the job completes successfully with no LLM usage.

Batch extract metadata (template-level)

POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/extract-all/

Batch-extract metadata for all files mapped to a template. Async — returns a job_id for tracking.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Response (200 OK):

{
  "result": true,
  "job_id": "aj_aBcDeFgHiJkLmN",
  "template_id": "mt_oPqRsTuVwXyZ12"
}

Maximum 1,000 files processed per job.

Template Assignments (folder-level)

Templates can be assigned to individual folder nodes (or workspace-level), independently of the per-file node map. These endpoints manage those folder assignments.

Assign a template to a folder

POST /current/workspace/{workspace_id}/metadata/template/assign/

Assign a metadata template to a folder node, or workspace-level when node_id is omitted. When a node_id is supplied, the node is added to the template’s node map under the same per-plan node cap that /nodes/add enforces.

Auth: Bearer token required. Workspace admin permission. Metadata billing feature required.

ParameterTypeRequiredDescription
template_idstringYesOpaque template id to assign. Must belong to this workspace.
node_idstringNoFolder node opaque id. Omit (or send empty) for a workspace-level assignment.

Response (200 OK):

{
  "result": true,
  "node_id": "aBcDeFgHiJkLmN",
  "template_id": "9876543210987654321"
}

node_id echoes what you sent (null for a workspace-level assignment). If the node is already mapped to the template the call succeeds as a no-op.

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Missing/invalid template_id or node_id, or adding the node would exceed the per-plan template node cap
1609 (Not Found)404Template not found, or does not belong to this workspace
1658 (Not Acceptable)406Another assign for this template is in progress — retry shortly
1664 (Datastore Error)500Cap check or assignment failed

List template assignments

GET /current/workspace/{workspace_id}/metadata/template/assignments/

List the workspace’s (non-deleted) templates as assignment records.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Response (200 OK):

{
  "result": true,
  "count": 1,
  "items": [
    {
      "template_id": "mt_aBcDeFgHiJkLmN",
      "name": "Invoice Template",
      "created": "2026-07-07 16:00:00 UTC",
      "updated": "2026-07-07 16:30:00 UTC"
    }
  ]
}
FieldTypeDescription
countintegerNumber of assignment records returned
items[].template_idstringOpaque template id
items[].namestringTemplate name
items[].createdstringCreation timestamp (YYYY-MM-DD HH:MM:SS UTC)
items[].updatedstringLast update timestamp (YYYY-MM-DD HH:MM:SS UTC)

Error responses:

Error CodeHTTP StatusCause
1664 (Datastore Error)500Failed to list template assignments

Resolve the effective template for a node

GET /current/workspace/{workspace_id}/metadata/template/resolve/{node_id}/

Resolve the metadata template mapped to a storage node by walking the node map. Returns the first mapped template, or null when the node has no mapping.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

{node_id} is a path segment (a storage node opaque id).

Response (200 OK):

{
  "result": true,
  "template": {
    "id": "mt_aBcDeFgHiJkLmN",
    "name": "Invoice Template",
    "fields": []
  }
}

When the node has no mapped template the response is { "result": true, "template": null }. The template object, when present, is the full template resource (see Template Resource Schema).

Unassign a template from a folder

DELETE /current/workspace/{workspace_id}/metadata/template/unassign/

Remove a template assignment from a folder node. Because this is a DELETE, pass the parameters as query string (a DELETE body is not read).

Auth: Bearer token required. Workspace admin permission. Metadata billing feature required.

ParameterTypeRequiredDescription
node_idstringNoFolder node opaque id (query param). Omit for the workspace-level no-op.
template_idstringConditionalRequired when node_id is supplied.

Response (200 OK):

{ "result": true }

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Invalid node_id, or missing/invalid template_id when a node_id is supplied
1664 (Datastore Error)500Failed to remove the assignment

Node Metadata

Metadata stored on individual files. Split into template metadata (conforming to mapped templates) and custom metadata (user-defined fields).

Get file metadata

GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/details/

Returns all metadata for a file. Response contains template_metadata and custom_metadata separately.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Request example:

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/aBcDeFgHiJkLmN/metadata/details/" \
  -H "Authorization: Bearer {jwt_token}"

Response (200 OK):

{
  "result": true,
  "instance_id": "1234567890123456789",
  "object_id": "aBcDeFgHiJkLmN",
  "template_id": "mt_oPqRsTuVwXyZ12",
  "node_id": {
    "id": "aBcDeFgHiJkLmN",
    "name": "invoice_2025.pdf",
    "type": "file",
    "size": 245760
  },
  "metadata": [
    {
      "key": "invoice_number",
      "description": "Invoice number",
      "type": "string",
      "value": "INV-2025-001",
      "is_auto": false,
      "updated": "2025-01-20 10:30:00 UTC"
    },
    {
      "key": "amount",
      "description": "Invoice amount",
      "type": "float",
      "value": 1500.00,
      "is_auto": true,
      "updated": "2025-01-20 10:30:00 UTC"
    }
  ]
}
FieldTypeDescription
response.template_idstring/nullAssociated template ID, or null
response.node_idobjectStorage node resource with file details
response.metadata[].keystringMetadata field key
response.metadata[].typestringValue type (string, int, float, bool, json, url, datetime)
response.metadata[].valuemixedThe metadata value
response.metadata[].is_autobooleanWhether the value was auto-generated by AI extraction
response.metadata[].updatedstringLast update timestamp

Bulk Form

The metadata details endpoint accepts a comma-separated list of node ids in place of a single {node_id}:

GET /current/workspace/{workspace_id}/storage/{id1},{id2},{id3}/metadata/details/

Up to 25 ids per call. Duplicate ids are silently deduplicated. Empty segments (e.g. trailing comma) return 400.

The bulk response shape differs from the single-id form. Successfully resolved objects appear under objects; per-id failures appear in a parallel errors array. Template definitions are hoisted to a top-level templates map keyed by template_id so a template shared by N objects is returned once instead of N times — clients look up each object's template via the template_id it carries. HTTP status is 200 when at least one object resolves and 404 when every requested id errored.

curl Example

curl -X GET "https://api.fast.io/current/workspace/1234567890123456789/storage/abc123,def456,ghi789/metadata/details/" \
  -H "Authorization: Bearer {jwt_token}"

Response (HTTP 200)

{
  "result": true,
  "format": "multi",
  "objects": [
    {
      "instance_id": "1234567890123456789",
      "object_id": "abc123",
      "template_id": "mt_oPqRsTuVwXyZ12",
      "node_id": { "id": "abc123", "name": "invoice_2025.pdf", "type": "file" },
      "template_metadata": [
        { "key": "invoice_number", "description": "Invoice number", "type": "string", "value": "INV-2025-001", "is_auto": false, "updated": "2025-01-20 10:30:00 UTC" }
      ],
      "custom_metadata": [],
      "autoextractable": true
    },
    {
      "instance_id": "1234567890123456789",
      "object_id": "def456",
      "template_id": "mt_oPqRsTuVwXyZ12",
      "node_id": { "id": "def456", "name": "invoice_2026.pdf", "type": "file" },
      "template_metadata": [
        { "key": "invoice_number", "description": "Invoice number", "type": "string", "value": "INV-2026-014", "is_auto": true, "updated": "2026-02-04 09:12:00 UTC" }
      ],
      "custom_metadata": [],
      "autoextractable": true
    }
  ],
  "templates": {
    "mt_oPqRsTuVwXyZ12": {
      "template_id": "mt_oPqRsTuVwXyZ12",
      "name": "Invoice",
      "fields": [
        { "name": "invoice_number", "type": "string", "description": "Invoice number" }
      ]
    }
  },
  "errors": [
    { "node_id": "ghi789", "code": 191049, "message": "Storage node not found" }
  ]
}

errors is always an array (possibly empty); templates is always an object/map (possibly empty {}).

Request-level Errors (whole request fails)

Error CodeSub-codeHTTP StatusDescription
1605 (Invalid Input)160655406Empty segment between commas
1605 (Invalid Input)109184406More than 25 unique ids in one request
1609 (Not Found)404Every requested id errored (errors array is populated)

Per-id Error Codes (inside each errors[] entry)

The request itself is HTTP 200 unless every id errored. Each entry in errors[] carries one of:

CodeMeaning
147196Invalid storage node id format
196136The literal root sentinel was supplied (only files/notes are valid)
191049Storage node not found
190770Backend error retrieving the storage node (any non-not-found failure)
150183Storage node exists but is not a file or note (e.g. a folder)
157684Backend failure retrieving the metadata key/value rows

Update file metadata

POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/update/{template_id}/

Set or update metadata key-value pairs on a file.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
key_valuesstring (JSON)YesJSON-encoded object of key-value pairs. Keys must match template field names.

Request example:

curl -X POST "https://api.fast.io/current/workspace/1234567890123456789/storage/aBcDeFgHiJkLmN/metadata/update/mt_oPqRsTuVwXyZ12/" \
  -H "Authorization: Bearer {jwt_token}" \
  -d 'key_values={"invoice_number":"INV-2025-001","amount":1500.00}'

Existing metadata for the same keys is overwritten. Keys not included in the request are left unchanged.

Delete file metadata

DELETE /current/workspace/{workspace_id}/storage/{node_id}/metadata/

Delete metadata keys from a file.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
keysstring (JSON)NoJSON-encoded array of key names to delete. If omitted, all keys may be removed.

Only files and notes support metadata — folders return an error.

Extract metadata (single file)

POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/extract/

Enqueues an AI extraction job for a single file against a template. Asynchronous — returns HTTP 202 Accepted with a job descriptor. Poll GET /current/workspace/{workspace_id}/jobs/status/ to track progress, then read the values from GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/details/ once the job reports completed. Supports documents, spreadsheets, images (PNG, JPEG, WebP), and code files. Extracted values are stored with is_auto: true.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
template_idstringNoThe template ID to extract against. Defaults to the first template mapped to the file.
fieldsstring (JSON)NoJSON-encoded array of field names to restrict extraction scope. Each name must exist on the template. Omit to extract the full schema.

Response (HTTP 202 Accepted):

{
  "result": true,
  "job_id": "{job_id}",
  "template_id": "{template_id}",
  "node_id": "{node_id}",
  "fields": ["amount", "due_date"],
  "status": "queued",
  "status_uri": "/workspace/{workspace_id}/jobs/status"
}

status is queued for a fresh job, or in_progress when the request is collapsed into an already-running job (see idempotent retries below).

Idempotent retries: submitting the same (node, template, fields) combination while a job is already in flight returns the existing job_id instead of enqueueing a duplicate. Retries within a 30-minute window are safe.

Polling flow:

POST /current/workspace/{id}/storage/{node}/metadata/extract/
  template_id={template_id}
  -> HTTP 202
     { "job_id": "{job_id}", "status": "queued",
       "status_uri": "/workspace/{id}/jobs/status" }

GET  /current/workspace/{id}/jobs/status/
  -> jobs.metadata_extract[] includes
     { "kind": "single",
       "active": true,
       "node_id": "{node_id}",
       "template_id": "{template_id}",
       "job_id": "{job_id}",
       "status": "in_progress",
       "progress_percent": 0 }

GET  /current/workspace/{id}/jobs/status/     (later)
  -> entry now { "status": "completed",
                 "progress_percent": 100,
                 "completed_at": ... }

GET  /current/workspace/{id}/storage/{node}/metadata/details/
  -> extracted values under `template_metadata`

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Node is root, fields references an unknown field, or payload is malformed
1609 (Not Found)404Template not found
1609 (Not Found)404Node not found
1664 (Datastore Error)500Failed to enqueue extraction job
1696 (Credits Exhausted)402No AI credits remaining

Batch extract metadata for a folder

POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/extract-all/

Enqueue an async job that runs metadata extraction on every file in a folder against the workspace’s template. This is the folder-level counterpart to the single-file /metadata/extract/ (one file) and the template-level /metadata/templates/{template_id}/extract-all/ (all files mapped to one template).

Auth: Bearer token required. Workspace admin permission. Metadata billing feature required. Conservative throttle (these operations are expensive).

{node_id} is a folder node opaque id, or the literal root alias for the workspace root.

Response (200 OK):

{
  "result": true,
  "job_id": "aj_aBcDeFgHiJkLmN",
  "template_id": "mt_oPqRsTuVwXyZ12"
}

The job runs asynchronously against the workspace’s active template — poll GET /current/workspace/{workspace_id}/jobs/status/ (the jobs.metadata_extract[] array) for progress.

Error responses:

Error CodeHTTP StatusCause
1605 (Invalid Input)406Missing/invalid folder id, or the node is not a folder
1609 (Not Found)404Folder not found, or no template is configured for the workspace
1664 (Datastore Error)500Failed to list templates or enqueue the job

List metadata by template

GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/list/{template_id}/

List all files with metadata for a specific template. Supports filtering and sorting.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

ParameterTypeRequiredDescription
filtersstring (JSON)NoJSON-encoded filter criteria
order_bystringNoField key name to sort by
order_descstringNo"true" or "false" for descending sort

List templates in use

GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/templates/

List templates that have metadata set on files, with object counts.

Auth: Bearer token required. Workspace member. Metadata billing feature required.

Metadata versions

GET /current/workspace/{workspace_id}/storage/{node_id}/metadata/versions/

List metadata version snapshots for a file. Useful for tracking changes to extracted metadata over time.

Metadata Saved Views

Each user can persist per-template column order, column visibility, and sort preferences. Saved views are private to the user — two users can independently customize the same template. Identity is the tuple (workspace, user, template); there is at most one saved view per user per template per workspace. Full endpoint reference is in the Storage Operations docs.

GET /current/workspace/{workspace_id}/metadata/view/?template_id={template_id}
POST /current/workspace/{workspace_id}/metadata/view/
DELETE /current/workspace/{workspace_id}/metadata/view/?template_id={template_id}
GET /current/workspace/{workspace_id}/metadata/views/
POST /current/workspace/{workspace_id}/metadata/view/{template_id}/export/

Auth required. Workspace member. Metadata billing feature required.

POST body: application/x-www-form-urlencoded with template_id (string) and config (JSON-encoded string containing the view config). Do NOT send as application/json — the endpoint only accepts form-encoded bodies and returns 406 (This field is missing) for JSON bodies. The config schema (the JSON serialized into the form field) is { version: 1, columns: [{field, visible?, width?}], sort: {field, dir}, filters: [{field, operator, value_type, value}] }; column array order IS the display order; dir is asc or desc; filters are AND-chained, max 5 per view, with operator in = != < <= > >= and value_type in string | int | float | bool (json rejected). Each saved-view filter entry uses field for its template-field identifier (matching columns[].field and sort.field); the nodes endpoint's stateless filters query parameter keeps its pre-existing key form, and the server handles the translation when auto-applying a saved view. Unknown keys are rejected with 1605 (Invalid Input). See the Storage Operations docs for the full filter contract.

The template nodes listing endpoint (GET /workspace/{workspace_id}/metadata/templates/{template_id}/nodes) auto-applies config.sort and config.filters from the caller's saved view when the corresponding sort_field / filters query parameters are not passed explicitly. An explicit query parameter (even empty) always wins; no server-side merge.

A saved view can also be exported to a TSV file: POST /workspace/{workspace_id}/metadata/view/{template_id}/export/ queues an asynchronous export written to workspace storage, applying the view's columns, sort, and filters. A saved view must already exist for that template. The export runs in the background — poll the destination folder for the resulting file. See the Storage Operations docs for the export parameters, deduplication behavior, and row limit.

Jobs Status (Unified Async Processing)

A single endpoint to check the status of all async processing jobs (AI indexing, metadata extraction) for a workspace or share. Replaces the removed metadata/intelligence/status and metadata/templates/{id}/extract-status endpoints.

GET /current/workspace/{workspace_id}/jobs/status/

Workspace jobs status. Auth: Workspace member. Feature gate: AI feature must be enabled on the organization plan.

GET /current/share/{share_id}/jobs/status/

Share jobs status. Auth: Share viewer. Feature gate: AI feature must be enabled on the organization plan.

Response (200 OK):

{
  "result": true,
  "jobs": {
    "intelligence": {
      "active": true,
      "status": "ingesting",
      "direction": "enable",
      "total_files": 100,
      "eligible_files": 80,
      "processed": 30,
      "skipped": 5,
      "failed": 0,
      "progress_percent": 37,
      "started_at": 1711500000,
      "updated_at": 1711500300,
      "completed_at": null,
      "stop_reason": null
    },
    "metadata_extract": [
      {
        "kind": "batch",
        "active": true,
        "template_id": "1234567890123456789",
        "node_id": null,
        "job_id": "{batch_job_id}",
        "status": "extracting",
        "total_files": 50,
        "eligible_files": 40,
        "processed": 24,
        "skipped": 2,
        "failed": 0,
        "progress_percent": 60,
        "started_at": 1711500000,
        "updated_at": 1711500200,
        "completed_at": null,
        "stop_reason": null,
        "error_message": null,
        "fields_scope": ["name", "date", "amount"]
      },
      {
        "kind": "single",
        "active": true,
        "template_id": "1234567890123456789",
        "node_id": "{node_id}",
        "job_id": "{job_id}",
        "status": "in_progress",
        "total_files": 1,
        "eligible_files": 1,
        "processed": 0,
        "skipped": 0,
        "failed": 0,
        "progress_percent": 0,
        "started_at": 1711500400,
        "updated_at": 1711500400,
        "completed_at": null,
        "stop_reason": null,
        "error_message": null,
        "fields_scope": ["amount", "due_date"]
      }
    ]
  }
}
FieldTypeDescription
jobs.intelligenceobject/nullAI indexing job status, or null if no job exists
jobs.intelligence.statusstringstarting, ingesting, flushing, draining, completed, failed, or stopped
jobs.intelligence.directionstringenable (indexing files) or disable (removing embeddings)
jobs.intelligence.progress_percentinteger0–100 progress based on processed/eligible
jobs.metadata_extractarrayMixed extraction statuses (empty array if none). Each entry is either a per-template batch job or a per-node single-file job.
jobs.metadata_extract[].kindstring"batch" for per-template batch extraction, "single" for per-node single-file extraction
jobs.metadata_extract[].node_idstring/nullNode identifier for kind: "single" entries; null for kind: "batch"
jobs.metadata_extract[].statusstringBatch: queued, starting, walking, extracting, or completed states. Single: queued, in_progress, completed, or errored.
jobs.metadata_extract[].fields_scopearray/nullTemplate field names being extracted, or null for full-schema extraction

Both intelligence and extraction entries share the common progress fields (active, total_files, eligible_files, processed, skipped, failed, progress_percent, started_at, updated_at, completed_at, stop_reason). For kind: "single" entries, total_files and eligible_files are always 1 and progress_percent is 0 while pending or 100 on completion. Completed or errored entries older than one hour are hidden from the listing.

Real-time updates: Both job types broadcast via the Activity/WebSocket system. Clients subscribed to the workspace or share WebSocket channel receive activity notifications when progress changes, reducing the need for polling.

Template Resource Schema

FieldTypeDescription
idstringOpaque template identifier
instanceIdstringWorkspace ID
orgIdstring/nullOrganization ID
namestringTemplate name
descriptionstringTemplate description
lockedbooleanWhether the template is locked from editing
priorityinteger/nullPriority level 1–5, or null
enabledbooleanWhether enabled for the workspace
deletedstring/nullSoft-delete timestamp, or null
updatedstringLast updated timestamp
createdstringCreation timestamp
fieldsarrayArray of field definition objects
node_countintegerNumber of files mapped to the template
field_countintegerNumber of fields declared in the template schema

The template resource does not include a category field.

Supported Field Types

TypeDescription
stringText values (max 10,000 characters)
intInteger numbers
floatDecimal numbers
boolBoolean true/false
jsonComplex JSON structures
urlValidated URLs
datetimeDate and time values

Quick Reference

Create a chat and get the answer

POST /current/workspace/{id}/ai/agent/
  question=...
  -> thread.thread_id, turn.turn_id

GET  /current/activity/poll/{id}?wait=95&lastactivity=...
  -> wait for ai_chat:{chatId}

GET  /current/workspace/{id}/ai/agent/{chat_id}/message/{turn_id}/details/
  -> check status == complete

GET  /current/workspace/{id}/ai/agent/{chat_id}/message/{turn_id}/read/
  -> SSE stream: data, commentary, status, analysis_data, table_data, done

Create a note (bank knowledge for RAG)

POST /current/workspace/{id}/storage/{parent}/createnote/
  name=research-notes.md&content=...

Extract metadata from a file (async)

POST /current/workspace/{id}/storage/{node}/metadata/extract/
  template_id={template_id}
  -> HTTP 202 { job_id, status: "queued" | "in_progress" }

GET  /current/workspace/{id}/jobs/status/
  -> jobs.metadata_extract[] (kind: "single") reaches status: "completed"

GET  /current/workspace/{id}/storage/{node}/metadata/details/
  -> extracted values

List eligible nodes for metadata (files and notes)

GET /current/workspace/{id}/metadata/eligible/

Add nodes to a template (files and notes)

POST /current/workspace/{id}/metadata/templates/{template_id}/nodes/add/
  node_ids=["nodeId1","nodeId2"]

AI auto-match files to a template

POST /current/workspace/{id}/metadata/templates/{template_id}/auto-match/

Batch extract metadata for all files in a template

POST /current/workspace/{id}/metadata/templates/{template_id}/extract-all/
  -> returns job_id (rate limited)

Check status of all async jobs (intelligence + metadata extraction)

GET /current/workspace/{id}/jobs/status/
GET /current/share/{id}/jobs/status/
  -> returns jobs.intelligence and jobs.metadata_extract[]

Semantic search (use /storage/search instead — /ai/search is deprecated)

GET /current/workspace/{id}/storage/search/?search=quarterly+revenue&limit=10
GET /current/workspace/{id}/storage/search/?search=quarterly+revenue&details=true

Optional details=true includes full node resource (previews, AI state, metadata, size) per result. Default limit drops to 10 when details enabled.

Share AI chat (same workflow as workspace)

POST /current/share/{id}/ai/agent/
  question=...
  -> thread.thread_id, turn.turn_id

GET  /current/share/{id}/ai/agent/{chat_id}/message/{turn_id}/details/
  -> check status == complete

GET  /current/share/{id}/ai/agent/{chat_id}/message/{turn_id}/read/
  -> SSE stream: data, commentary, status, analysis_data, table_data, done

Share-specific AI

GET  /current/share/{id}/ai/autoog/
  -> binary PNG image (OG image)

POST /current/share/{id}/ai/autotitle/
  -> title, description, display_type

POST /current/share/{id}/ai/share/
  files=["opaqueId1","opaqueId2"]
  -> markdown with download URLs
↑ Back to top