AI & Chat RAG-powered chat, semantic search, notes, metadata extraction, and auto-summarization
Overview
Fast.io provides built-in AI capabilities for workspaces and shares:
- RAG-powered chat — Ask questions about indexed files with citations to specific pages and snippets
- General chat — AI conversation with optional file attachments for one-off analysis
- Auto-summarization — AI-generated titles and descriptions for shares
- Metadata extraction — AI-powered structured metadata extraction from documents, spreadsheets, images, and code
- Semantic search — Find files by meaning, not just keywords
- Notes — Markdown storage nodes that are indexed for RAG, letting you bank knowledge over time
AI chat is read-only. It can read, analyze, search, and answer questions about files, but it cannot modify files, change settings, manage members, or access events. All write operations must be done through the API directly.
Plan Requirements
AI capabilities are gated by two billing-plan features that work together:
content_ai— master switch for AI features. Covers read-only AI surfaces: listing chats, reading chat details and message history, streaming existing responses, auto-title / auto-OG generation, and notes. Available on all paid plans.ai_agent— narrower gate for interactive agentic flows. Required to create chats, send chat messages, and enable theintelligencetoggle on a workspace or portal share (the toggle controls the RAG indexing pipeline that feeds the chat surface).
Current plan coverage:
| Plan | content_ai | ai_agent | Effect |
|---|---|---|---|
| Free | off | off | AI chat and intelligence unavailable |
| Agent (free) | on | off | Read-only AI surfaces; cannot create chats, send messages, or enable intelligence |
| Pro | on | on | Full AI (chat, intelligence, RAG) |
| Business | on | on | Full AI (chat, intelligence, RAG) |
How gated endpoints respond when a plan lacks the feature:
- Create chat / send message:
1695 (Upgrade Required)→ HTTP 402 - Attempt to set
intelligence=trueon create/update:1605 (Invalid Input)with a message indicating intelligence requires a plan that supports agentic AI
Agent-plan accounts can still ingest, store, search, and share files; transfer org ownership to a human on a paid plan to unlock agentic chat. See the Organizations reference for transfer flow.
Intelligence Setting
The intelligence boolean on a workspace or portal share controls whether uploaded files are automatically indexed for RAG.
intelligence=true— Files are auto-indexed for semantic search, summarization, and citation. Required forchat_with_filestype chats with file/folder scope. Requires bothcontent_aiandai_agentplan features (see Plan Requirements above). On agent-plan accountsintelligencedefaults tofalseon new workspaces because the agent plan does not includeai_agent; on plans that includeai_agent, agent accounts default totrue.intelligence=false— Files are stored/shared without RAG indexing. You can still usechattype withfiles_attachfor direct file analysis (when the plan supports chat).
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:
/current/org/{org_id}/create/workspace/
Pass intelligence=true
Update later:
/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.
Chat Types
Two chat types are available. Both augment answers with web knowledge.
chat — General AI Conversation
- Does not use RAG or workspace file indexing
- Works regardless of the workspace
intelligencesetting - You can attach files directly via
files_attachfor one-off analysis (e.g., “Describe this image”, “Summarize this PDF”) - Any file with a ready preview or AI summary can be attached
chat_with_files — RAG-Powered Chat
- Searches indexed files in the workspace/share (or a scoped folder/file set)
- Returns answers with citations to specific files, pages, and text snippets
- Requires the workspace/share
intelligencesetting to be enabled when using file/folder scope - Default scope is the entire workspace/share — omit scope parameters to search all indexed files
- Only files with
ai_state: indexedare included in RAG searches
File Scope vs File Attachments
These are two different ways to give the AI context. They cannot be combined in the same request.
Folder/File Scope (files_scope, folders_scope)
Limits the RAG search space. The AI searches indexed files within the specified scope and uses matching content as citations.
- Default scope is the entire workspace/share — omit both
files_scopeandfolders_scopeto search all indexed files - Only files with
ai_state: indexedare included - Only used with
chat_with_filestype
files_scope — comma-separated nodeId:versionId pairs (max 100). Both nodeId and versionId are required and must be non-empty in each pair. Get the versionId from the file’s version field in storage list/details responses.
folders_scope — comma-separated nodeId:depth pairs (max 100 subfolder references). Depth controls how many levels of subfolders are enumerated (1–10). Only subfolder references count toward the limit, not individual files. The RAG backend searches all indexed files within the scoped folders automatically.
You do not need to enumerate individual files. Just provide top-level folder IDs with depth.
File Attachments (files_attach)
Files are directly attached to the chat message for analysis. Use this for direct analysis tasks. Only file nodes are accepted—folder nodes are rejected. To include folder contents, use folders_scope instead.
- Files must have AI summaries ready (be in a “ready” state or eligible for summarization)
- Max 20 files, 200 MB total (effective size after preview optimization)
- Works with both
chatandchat_with_filestypes - Good for: “Describe this image”, “Extract action items from this transcript”, “Compare these two contracts”
Format: comma-separated nodeId:versionId pairs, e.g., files_attach=nodeId1:versionId1,nodeId2:versionId2
Choosing Between Scope and Attachments
| Use Case | Mechanism | Chat Type |
|---|---|---|
| Search across many indexed files | files_scope / folders_scope | chat_with_files |
| Analyze specific files directly | files_attach | chat or chat_with_files |
| Ask general questions about the workspace | Omit scope (defaults to all files) | chat_with_files |
| General conversation, no files | Neither | chat |
AI State (File Readiness)
Files in an intelligent workspace progress through AI processing states:
| State | Meaning |
|---|---|
disabled | Intelligence not enabled for this file/workspace |
pending | Queued for AI processing |
in_progress | Currently being processed by AI |
ready | File 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. |
indexed | File contents have been indexed via RAG. Indexed files are searchable by semantic meaning and their content is used as grounding in scoped AI chats. |
failed | AI 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.
Personality Parameter
The personality parameter controls the length and style of AI responses. Pass it when creating a chat or sending a message.
| Value | Behavior |
|---|---|
concise | Short, brief, direct answers with minimal elaboration |
detailed | Comprehensive answers with context, evidence, and examples (default) |
You can also control verbosity directly in your question phrasing:
- “In one sentence, what is the main conclusion?”
- “List only the file names that mention GDPR, no explanations”
- “Give me a brief summary — 2–3 bullet points max”
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
/current/workspace/{workspace_id}/storage/{parent_id}/createnote/
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Note name, must end in .md |
| content | string | Yes | Markdown content, max 100 KB |
{parent_id} is a folder OpaqueId or "root". Returns the created note as a node resource.
Update a note
/current/workspace/{workspace_id}/storage/{node_id}/updatenote/
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | New name, must end in .md |
| content | string | No | New markdown content, max 100 KB |
At least one of name or content must be provided. Updating content creates a new version.
Read note content
/current/workspace/{workspace_id}/storage/{node_id}/read/
Returns the raw markdown content.
Linking a user to a note
- In workspace context:
https://{org_domain}.fast.io/workspace/{workspace_name}?note={note_opaque_id} - Direct preview: use the preview URL for the note node
Workspace AI Endpoints
Create a new chat
/current/workspace/{workspace_id}/ai/chat/
Creates a chat with an initial message. The AI begins processing asynchronously.
Auth: Bearer token required. Workspace view permission. content_ai and ai_agent plan features required.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | Yes | — | chat or chat_with_files |
| question | string | Yes | — | Initial question, 2–12,768 characters |
| privacy | string | No | public | private or public |
| name | string | No | Auto-generated | Chat name. Auto-generated from first 10 words of question if omitted. |
| personality | string | No | detailed | concise or detailed |
| files_scope | string | No | All indexed files | Comma-separated nodeId:versionId pairs (max 100). Requires chat_with_files type and Intelligence enabled. |
| folders_scope | string | No | All indexed files | Comma-separated nodeId:depth pairs (max 100, depth 1–10). Requires chat_with_files type and Intelligence enabled. |
| files_attach | string | No | — | Comma-separated file nodeId:versionId pairs (max 20, 200 MB total). Only file nodes accepted—folder nodes are rejected. Cannot be combined with files_scope/folders_scope. Files must have AI summaries ready. |
Request example:
curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/" \
-H "Authorization: Bearer {jwt_token}" \
-d "type=chat_with_files" \
-d "question=What were the Q3 revenue figures?" \
-d "privacy=private" \
-d "personality=detailed"
Response (200 OK):
{
"result": "yes",
"response": {
"chat": {
"id": "aBcDeFgHiJkLmNoPqR",
"message": {
"id": "xYzAbCdEfGhIjKlMnO"
}
}
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.chat.id | string | Opaque ID of the created chat |
response.chat.message.id | string/null | Opaque ID of the initial message, or null if no question was provided |
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 400 | Invalid type, privacy, or personality value |
1605 (Invalid Input) | 400 | File/folder scope used without Intelligence enabled |
1605 (Invalid Input) | 400 | Both files_attach and files_scope specified |
1605 (Invalid Input) | 400 | Invalid name or other property |
1664 (Datastore Error) | 500 | Chat or message creation failed |
List chats
/current/workspace/{workspace_id}/ai/chat/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.
Variant: Append /deleted to the path to list deleted chats: GET .../ai/chat/list/deleted
Request example:
curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/list/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"chats": [
{
"chat_id": "aBcDeFgHiJkLmNoPqR",
"creator": { "type": "user", "id": "12345678901234567890" },
"type": "chat_with_files",
"name": "Quarterly report analysis",
"status": "active",
"message_count": 5,
"latest_message": {
"message_id": "xYzAbCdEfGhIjKlMnO",
"creator": { "type": "user", "id": "12345678901234567890" },
"state": "complete",
"message": { "text": "Summarize the quarterly report" },
"response": null,
"created": "2025-06-15T10:30:00Z",
"updated": "2025-06-15T10:30:05Z"
},
"unique_creators": [
{ "type": "user", "id": "12345678901234567890" }
],
"cost": { "credits": { "tokens": 1500 } },
"efficiency": "good",
"privacy": {
"visibility": "private",
"owner": { "type": "user", "id": "12345678901234567890" }
},
"created_at": "2025-06-15T10:00:00Z",
"updated_at": "2025-06-15T10:30:05Z"
}
]
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.chats | array | Array of chat objects |
response.chats[].chat_id | string | Opaque ID of the chat |
response.chats[].creator | object | {type, id} — the chat creator |
response.chats[].type | string | chat or chat_with_files |
response.chats[].name | string | Chat display name |
response.chats[].status | string | Chat status |
response.chats[].message_count | integer | Total messages in the chat |
response.chats[].latest_message | object | Most recent message preview |
response.chats[].unique_creators | array | Distinct participants |
response.chats[].cost.credits.tokens | integer | Total token cost |
response.chats[].efficiency | string | Efficiency rating |
response.chats[].privacy | object | {visibility, owner} |
response.chats[].created_at | string | Creation timestamp (YYYY-MM-DD HH:MM:SS) |
response.chats[].updated_at | string | Last update timestamp (YYYY-MM-DD HH:MM:SS) |
Get chat details
/current/workspace/{workspace_id}/ai/chat/{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/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/details/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"chat": {
"chat_id": "aBcDeFgHiJkLmNoPqR",
"creator": { "type": "user", "id": "12345678901234567890" },
"type": "chat_with_files",
"name": "Quarterly report analysis",
"status": "active",
"message_count": 3,
"messages": [
{
"chat_id": "aBcDeFgHiJkLmNoPqR",
"message_id": "xYzAbCdEfGhIjKlMnO",
"creator": { "type": "user", "id": "12345678901234567890" },
"personality": "detailed",
"state": "complete",
"message": {
"text": "Summarize the quarterly report",
"author_name": "John",
"scope": {},
"attached": []
},
"response": {
"text": "The quarterly report shows revenue growth of 15%...",
"error": false,
"created": "2025-06-15T10:30:05Z",
"cost": { "tokens": 500, "details": {} },
"author_name": "Ripley",
"events": [],
"table_data": [],
"analysis_chunks": [],
"citations": [
{
"hash": "a1b2c3d4",
"nodeId": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4",
"versionId": "v1abc-defgh-ijklm",
"entries": [
{ "page": 3, "snippet": "Revenue increased by 15% year over year...", "timestamp": null }
]
}
]
},
"created": "2025-06-15T10:30:00Z",
"updated": "2025-06-15T10:30:05Z"
}
],
"unique_creators": [ { "type": "user", "id": "12345678901234567890" } ],
"cost": { "credits": { "tokens": 1500 } },
"efficiency": "good",
"privacy": { "visibility": "private", "owner": { "type": "user", "id": "12345678901234567890" } },
"created_at": "2025-06-15T10:00:00Z",
"updated_at": "2025-06-15T10:30:05Z"
}
},
"current_api_version": "1.0"
}
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1609 (Not Found) | 404 | Chat ID not found |
1654 (Internal Error) | 500 | Internal failure loading chat |
Update a chat
/current/workspace/{workspace_id}/ai/chat/{chat_id}/update/
Update the name of an existing chat.
Auth: Bearer token required. content_ai plan feature required.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | New chat name |
Request example:
curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/update/" \
-H "Authorization: Bearer {jwt_token}" \
-d "name=Updated Chat Name"
Response (200 OK):
{ "result": "yes", "current_api_version": "1.0" }
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1605 (Invalid Input) | 400 | Invalid name value |
1664 (Datastore Error) | 500 | Update failed |
Delete a chat
/current/workspace/{workspace_id}/ai/chat/{chat_id}/
Auth: Bearer token required. content_ai plan feature required.
Request example:
curl -X DELETE "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{ "result": "yes", "current_api_version": "1.0" }
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1654 (Internal Error) | 500 | Chat in non-deletable state or internal error |
Deleted chats can be listed via GET .../ai/chat/list/deleted.
Send a follow-up message
/current/workspace/{workspace_id}/ai/chat/{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.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| question | string | Yes | — | Follow-up question, 2–12,768 characters |
| personality | string | No | detailed | concise or detailed |
| files_scope | string | No | All indexed files | Comma-separated nodeId:versionId pairs (max 100). Only for chat_with_files type. |
| folders_scope | string | No | All indexed files | Comma-separated nodeId:depth pairs (max 100, depth 1–10). Only for chat_with_files type. |
| files_attach | string | No | — | Comma-separated file nodeId:versionId pairs (max 20, 200 MB total). Only file nodes accepted—folder nodes are rejected. Cannot be combined with scope params. |
The chat type is inherited from the chat itself — you do not need to specify type again.
Request example:
curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/" \
-H "Authorization: Bearer {jwt_token}" \
-d "question=How does that compare to Q2?"
Response (200 OK):
{
"result": "yes",
"response": {
"message": {
"id": "mNoPqRsTuVwXyZaBcD"
}
},
"current_api_version": "1.0"
}
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1605 (Invalid Input) | 400 | Both files_attach and files_scope specified |
1605 (Invalid Input) | 400 | Scope used with wrong chat type |
1654 (Internal Error) | 500 | Message creation failed |
List messages in a chat
/current/workspace/{workspace_id}/ai/chat/{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/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/messages/list/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"messages": [
{
"chat_id": "aBcDeFgHiJkLmNoPqR",
"message_id": "xYzAbCdEfGhIjKlMnO",
"creator": { "type": "user", "id": "12345678901234567890" },
"personality": "detailed",
"state": "complete",
"message": {
"text": "Summarize the quarterly report",
"author_name": "John",
"scope": {},
"attached": []
},
"response": {
"text": "The quarterly report shows...",
"error": false,
"created": "2025-06-15T10:30:05Z",
"cost": { "tokens": 500, "details": {} },
"author_name": "Ripley",
"events": [],
"table_data": [],
"analysis_chunks": [],
"citations": []
},
"created": "2025-06-15T10:30:00Z",
"updated": "2025-06-15T10:30:05Z"
}
]
},
"current_api_version": "1.0"
}
Get message details
/current/workspace/{workspace_id}/ai/chat/{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.
Key response fields:
| Field | Description |
|---|---|
state | Message processing state: ready, in_progress, complete, errored |
message.text | The original question text |
message.scope | File/folder scope used for RAG context |
message.attached | Attached files as {node_id, version_id} objects |
response.text | The AI response (available when state is complete) |
response.error | Whether an error occurred during processing |
response.cost | {tokens, details} — message token cost |
response.citations | Array of file citations (see Citation Format below) |
Only read the response when state is complete.
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1683 (Resource Missing) | 404 | Message not found in the chat |
1654 (Internal Error) | 500 | Internal retrieval failure |
Stream message response (SSE)
/current/workspace/{workspace_id}/ai/chat/{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/12345678901234567890/ai/chat/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 Type | Description |
|---|---|
data | Text chunks of the AI response. Payload: {"item": "..."}. Concatenate all data events for the full text. |
event | Status update or event notification |
analysis_data | Structured analysis data, citations, and references to source files |
table_data | Tabular data extracted or generated by the AI |
done | Stream complete. No more events will be sent. |
Behavior:
- The message must be in
in_progressorcompletestate - For completed messages, all existing chunks are sent immediately followed by
done - For in-progress messages, the connection stays open and polls for new chunks at intervals (~5 seconds)
- The connection auto-terminates after a maximum wait time
- Headers include
Cache-Control: no-cache, no-store, must-revalidate - This endpoint returns an SSE stream, NOT the standard JSON response envelope
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1683 (Resource Missing) | 404 | Message not found |
1605 (Invalid Input) | 400 | Message not in in_progress or complete state |
1683 (Resource Missing) | 404 | No SSE chunk data exists for this message |
1654 (Internal Error) | 500 | Internal streaming failure |
Publish a private chat
/current/workspace/{workspace_id}/ai/chat/{chat_id}/publish/
Makes a private chat public (visible to other workspace members). One-way operation — published chats cannot be made private again.
Auth: Bearer token required. content_ai plan feature required.
Response (200 OK):
{ "result": "yes", "current_api_version": "1.0" }
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1660 (Conflict) | 409 | Chat is already public |
1664 (Datastore Error) | 500 | Update failed |
Generate AI Share
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| files | array (JSON) | Yes | JSON array of file opaque IDs. Min 1, max 25. |
Request example:
curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/ai/share/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"files": ["aBcDeFgHiJkLmN", "oPqRsTuVwXyZ12"]}'
Response (200 OK):
{
"result": "yes",
"response": {
"markdown": "## Files\n\n### quarterly-report.pdf\n[Download](https://api.fast.io/...)\nSize: 2.5 MB\n\n..."
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.markdown | string | Generated markdown with file info and temporary download URLs |
Notes:
- Download URLs expire after 5 minutes (300 seconds)
- Each token can be used a maximum of 3 times
- Individual files limited to 50 MB; total size limited to 100 MB
- When more than 5 files: titles only. 5 or fewer: includes full descriptions.
- The
filesinput is a JSON array (not comma-separated strings)
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 400 | Empty files array |
1605 (Invalid Input) | 400 | More than 25 files |
List AI transactions
/current/workspace/{workspace_id}/ai/transactions/
Returns up to 40 most recent AI token usage transactions for the workspace. Workspace-only — no share equivalent.
Auth: Bearer token required. Workspace view permission. content_ai plan feature required.
Request example:
curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/ai/transactions/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"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"
}
]
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.count | integer | Number of transactions returned |
response.items[].id | string | Formatted transaction ID |
response.items[].type | string | Parent type (e.g., chat, chat_with_files) |
response.items[].status | string | Transaction status |
response.items[].tokens | integer | Token credits consumed |
response.items[].updated | string | Last update timestamp (UTC) |
response.items[].created | string | Creation timestamp (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/chat/" \
-H "Authorization: Bearer {jwt_token}" \
-d "question=What were the Q3 revenue figures?" \
-d "type=chat_with_files" \
-d "personality=detailed"
Response includes chat_id and 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/chat/{chat_id}/message/{message_id}/details/" \
-H "Authorization: Bearer {jwt_token}"
Message states: ready → in_progress → complete (or errored). Only read the response when state is complete.
4. Stream the response:
curl -N -X GET "https://api.fast.io/current/workspace/{workspace_id}/ai/chat/{chat_id}/message/{message_id}/read/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Accept: text/event-stream"
Returns SSE with event types: data (text chunks), analysis_data, table_data, done.
5. Send follow-ups:
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/ai/chat/{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 returned in the chat_id field when creating or 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
/current/share/{share_id}/ai/autoog/
Generates an Open Graph image for the share. Returns binary PNG image data (not JSON).
Auth: Conditional — required for private shares; optional for public shares.
| Behavior | Description |
|---|---|
| Public share | Custom AI-generated image based on share content |
| Private share | Default private image returned |
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1609 (Not Found) | 404 | Share is disabled |
1654 (Internal Error) | 500 | Default image not available |
Auto-generate title and description
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| context | string | No | Optional user-provided context to guide AI generation |
Request example:
curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/autotitle/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"title": "Q4 Financial Reports",
"description": "Quarterly financial reports and analysis for fiscal year 2025.",
"display_type": "document"
}
}
| Field | Type | Description |
|---|---|---|
response.title | string | AI-generated title |
response.description | string | AI-generated description |
response.display_type | string | AI-suggested display type |
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1680 (Access Denied) | 403 | Insufficient share permissions |
1654 (Internal Error) | 500 | Share 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:
- Auth uses share permissions instead of workspace permissions
- File scope references share files instead of workspace files
- No AI Transactions endpoint (workspace-only)
Create a new chat (Share)
/current/share/{share_id}/ai/chat/
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.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | Yes | — | chat or chat_with_files |
| question | string | Yes | — | Initial question, 2–12,768 characters |
| privacy | string | No | public | private or public |
| name | string | No | Auto-generated | Chat name. Auto-generated from first 10 words of question if omitted. |
| personality | string | No | detailed | concise or detailed |
| files_scope | string | No | All indexed files | Comma-separated nodeId:versionId pairs (max 100). Requires chat_with_files type and Intelligence enabled. |
| folders_scope | string | No | All indexed files | Comma-separated nodeId:depth pairs (max 100, depth 1–10). Requires chat_with_files type and Intelligence enabled. |
| files_attach | string | No | — | Comma-separated file nodeId:versionId pairs (max 20, 200 MB total). Only file nodes accepted—folder nodes are rejected. Cannot be combined with files_scope/folders_scope. Files must have AI summaries ready. |
Request example:
curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/" \
-H "Authorization: Bearer {jwt_token}" \
-d "type=chat_with_files" \
-d "question=What were the Q3 revenue figures?" \
-d "privacy=private" \
-d "personality=detailed"
Response (200 OK):
{
"result": "yes",
"response": {
"chat": {
"id": "aBcDeFgHiJkLmNoPqR",
"message": {
"id": "xYzAbCdEfGhIjKlMnO"
}
}
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.chat.id | string | Opaque ID of the created chat |
response.chat.message.id | string/null | Opaque ID of the initial message, or null if no question was provided |
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 400 | Invalid type, privacy, or personality value |
1605 (Invalid Input) | 400 | File/folder scope used without Intelligence enabled |
1605 (Invalid Input) | 400 | Both files_attach and files_scope specified |
1664 (Datastore Error) | 500 | Chat or message creation failed |
List chats (Share)
/current/share/{share_id}/ai/chat/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.
Variant: Append /deleted to the path to list deleted chats: GET .../ai/chat/list/deleted
Request example:
curl -X GET "https://api.fast.io/current/share/12345678901234567890/ai/chat/list/" \
-H "Authorization: Bearer {jwt_token}"
Response: Same format as workspace chat list. See List chats above.
Get chat details (Share)
/current/share/{share_id}/ai/chat/{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/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/details/" \
-H "Authorization: Bearer {jwt_token}"
Response: Same format as workspace chat details. See Get chat details above.
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1609 (Not Found) | 404 | Chat ID not found |
1654 (Internal Error) | 500 | Internal failure loading chat |
Update a chat (Share)
/current/share/{share_id}/ai/chat/{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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | New chat name |
Request example:
curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/update/" \
-H "Authorization: Bearer {jwt_token}" \
-d "name=Updated Chat Name"
Response (200 OK):
{ "result": "yes", "current_api_version": "1.0" }
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1605 (Invalid Input) | 400 | Invalid name value |
1664 (Datastore Error) | 500 | Update failed |
Delete a chat (Share)
/current/share/{share_id}/ai/chat/{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/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{ "result": "yes", "current_api_version": "1.0" }
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1654 (Internal Error) | 500 | Chat in non-deletable state or internal error |
Deleted chats can be listed via GET .../ai/chat/list/deleted.
Send a follow-up message (Share)
/current/share/{share_id}/ai/chat/{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.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| question | string | Yes | — | Follow-up question, 2–12,768 characters |
| personality | string | No | detailed | concise or detailed |
| files_scope | string | No | All indexed files | Comma-separated nodeId:versionId pairs (max 100). Only for chat_with_files type. |
| folders_scope | string | No | All indexed files | Comma-separated nodeId:depth pairs (max 100, depth 1–10). Only for chat_with_files type. |
| files_attach | string | No | — | Comma-separated file nodeId:versionId pairs (max 20, 200 MB total). Only file nodes accepted—folder nodes are rejected. Cannot be combined with scope params. |
The chat type is inherited from the chat itself — you do not need to specify type again.
Request example:
curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/" \
-H "Authorization: Bearer {jwt_token}" \
-d "question=How does that compare to Q2?"
Response (200 OK):
{
"result": "yes",
"response": {
"message": {
"id": "mNoPqRsTuVwXyZaBcD"
}
},
"current_api_version": "1.0"
}
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1605 (Invalid Input) | 400 | Both files_attach and files_scope specified |
1605 (Invalid Input) | 400 | Scope used with wrong chat type |
1654 (Internal Error) | 500 | Message creation failed |
List messages in a chat (Share)
/current/share/{share_id}/ai/chat/{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/12345678901234567890/ai/chat/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)
/current/share/{share_id}/ai/chat/{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/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/message/xYzAbCdEfGhIjKlMnO/details/" \
-H "Authorization: Bearer {jwt_token}"
Response: Same format as workspace message details. See Get message details above.
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1683 (Resource Missing) | 404 | Message not found in the chat |
1654 (Internal Error) | 500 | Internal retrieval failure |
Stream message response (SSE) (Share)
/current/share/{share_id}/ai/chat/{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/12345678901234567890/ai/chat/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 Code | HTTP Status | Cause |
|---|---|---|
1683 (Resource Missing) | 404 | Message not found |
1605 (Invalid Input) | 400 | Message not in in_progress or complete state |
1683 (Resource Missing) | 404 | No SSE chunk data exists for this message |
1654 (Internal Error) | 500 | Internal streaming failure |
Publish a private chat (Share)
/current/share/{share_id}/ai/chat/{chat_id}/publish/
Makes a private chat public (visible to other share members). One-way operation — published chats cannot be made private again.
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/12345678901234567890/ai/chat/aBcDeFgHiJkLmNoPqR/publish/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{ "result": "yes", "current_api_version": "1.0" }
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1658 (Not Acceptable) | 406 | Chat not found or locked |
1660 (Conflict) | 409 | Chat is already public |
1664 (Datastore Error) | 500 | Update failed |
Generate AI Share (Share)
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| files | array (JSON) | Yes | JSON array of file opaque IDs. Min 1, max 25. |
Request example:
curl -X POST "https://api.fast.io/current/share/12345678901234567890/ai/share/" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"files": ["aBcDeFgHiJkLmN", "oPqRsTuVwXyZ12"]}'
Response (200 OK):
{
"result": "yes",
"response": {
"markdown": "## Files\n\n### quarterly-report.pdf\n[Download](https://api.fast.io/...)\nSize: 2.5 MB\n\n..."
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.markdown | string | Generated markdown with file info and temporary download URLs |
Notes:
- Download URLs expire after 5 minutes (300 seconds)
- Each token can be used a maximum of 3 times
- Individual files limited to 50 MB; total size limited to 100 MB
- When more than 5 files: titles only. 5 or fewer: includes full descriptions.
- The
filesinput is a JSON array (not comma-separated strings)
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 400 | Empty files array |
1605 (Invalid Input) | 400 | More than 25 files |
1680 (Access Denied) | 403 | Insufficient download permissions |
Workspace AI vs. Share AI differences
| Feature | Workspace AI | Share AI |
|---|---|---|
| AI Transactions endpoint | Yes | No |
| Auto OG image endpoint | No | Yes |
| Auto title endpoint | No | Yes |
content_ai feature required | Yes (except AI Share) | Yes (except AI Share) |
ai_agent feature required | Yes, for create-chat and send-message (except AI Share) | Yes, for create-chat and send-message (except AI Share) |
| File scope context | Workspace files | Share files |
AI Share File Download
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| {token} | string (path) | Yes | Alphanumeric AI share token (generated by the AI Share creation endpoint) |
| file | integer (query) | Yes | Zero-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 Code | HTTP Status | Cause |
|---|---|---|
1609 (Not Found) | 404 | Token missing, invalid, expired, or use limit reached |
1609 (Not Found) | 404 | File index out of range |
1654 (Internal Error) | 500 | Unable to retrieve or read file |
All invalid/expired token errors return 404 to prevent token enumeration.
Semantic Search
/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.
Workspace Semantic Search (Deprecated)
/current/workspace/{workspace_id}/ai/search/
Auth: Bearer token required. Workspace view permission. content_ai plan feature required.
Share Semantic Search (Deprecated)
/current/share/{share_id}/ai/search/
Identical to workspace semantic search but scoped to a share.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| query_text | string | Yes | — | Search query, 2–1,000 characters |
| files_scope | string | No | All indexed files | Comma-separated nodeId:versionId pairs (max 100) |
| folders_scope | string | No | All indexed files | Comma-separated nodeId:depth pairs (max 100, depth 1–10) |
| limit | integer | No | 100 | Results per page, 1–500 |
| offset | integer | No | 0 | Pagination offset |
| details | string | No | false | When "true", each result includes a node field with the full node resource (previews, AI state, versions, metadata, size). Default limit drops to 10 (enrichment is expensive). An explicit limit overrides this default. |
Preferred approach — use /storage/search instead:
# Basic search
curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/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/12345678901234567890/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-rpjfm4",
"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
}
}
| Field | Type | Description |
|---|---|---|
results[].content | string | Matched text snippet from the indexed document |
results[].score | float | Relevance score (0.0–1.0, higher is more relevant) |
results[].node | object/null | Full node resource, or null if the file was deleted |
results[].file_details | object | Raw metadata when node is null (node_id, version_id, name, mimetype) |
pagination.total | integer | Total number of results |
pagination.has_more | boolean | Whether 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:
| Field | Type | Description |
|---|---|---|
relevance_score | float | Semantic relevance score (0.0–1.0) |
content_snippet | string/null | The actual matching text from semantic search. NULL for keyword-only matches. |
match_source | string | Source of the match: keyword, semantic, or both |
mimetype | string | File MIME type (e.g., application/pdf, audio/mpeg). Present for semantic matches. |
media_segment | object | Only for audio/video matches when intelligence is on. Contains start_seconds and end_seconds for deep-linking to the exact timestamp range. |
search_metadata | object | Additional 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": "yes",
"response": {
"files": {
"f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjfm4": {
"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-rpjfm4",
"name": "report.pdf",
"type": "file",
"size": 123456,
"previews": { "...": "..." },
"ai": { "state": "indexed" }
}
}
}
}
}
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 400 | Intelligence not enabled |
1605 (Invalid Input) | 400 | Malformed 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.
- Good: “What were the revenue figures for Q3 2025 compared to Q2?”
- Good: “Summarize the key findings from the compliance audit reports”
- Bad: “Tell me about these files” — too vague, no searchable content to match
With file attachments
You can be more direct since the AI has the full file content.
- Good: “Describe this image in detail” (with an image attached)
- Good: “Extract all action items from this meeting transcript”
- Good: “Compare these two contracts and list the differences”
Chat Session Object Schema
The chat resource is returned by details and list endpoints.
| Field | Type | Description |
|---|---|---|
chat_id | string | Opaque ID of the chat |
creator | object | {type: string, id: string} — the chat creator |
type | string | chat or chat_with_files |
name | string | Display name of the chat |
status | string | Current chat status |
message_count | integer | Total number of messages |
messages | array | Full message list (details endpoint) or omitted |
latest_message | object | Most recent message (list endpoint) or omitted |
unique_creators | array | Distinct participants as {type, id} objects |
cost | object | {credits: {tokens: int}} — total token cost |
efficiency | string | Efficiency rating |
privacy | object | {visibility: "private"|"public", owner: {type, id}|null} |
created_at | string | Creation timestamp (YYYY-MM-DD HH:MM:SS) |
updated_at | string | Last update timestamp (YYYY-MM-DD HH:MM:SS) |
Message Object Schema
| Field | Type | Description |
|---|---|---|
chat_id | string | Parent chat opaque ID |
message_id | string | Opaque ID of the message |
creator | object | {type: string, id: string} — message author |
personality | string | AI personality mode used (concise or detailed) |
state | string | Processing state: ready, in_progress, complete, errored |
message | object | User’s query (see sub-fields below) |
message.text | string | The question text |
message.author_name | string | User’s given name |
message.scope | object | File/folder scope used for RAG context |
message.attached | array | Attached files as {node_id, version_id} objects |
response | object/null | AI response (null if not yet available) |
response.text | string | AI response text |
response.error | boolean | Whether an error occurred during processing |
response.created | string | Response timestamp (YYYY-MM-DD HH:MM:SS) |
response.cost | object | {tokens: int, details: object} — message token cost |
response.author_name | string | AI persona name (e.g., Ripley) |
response.events | array | Status events generated during processing |
response.table_data | array | Structured table data in the response |
response.analysis_chunks | array | Analysis progress chunks |
response.citations | array | File citations (see below) |
Citation Format
Citations reference specific locations in files that informed the AI response.
| Field | Type | Description |
|---|---|---|
hash | string | File content hash (used for grouping) |
nodeId | string | Storage node opaque ID |
versionId | string | File version opaque ID |
entries | array | Citation locations within the file |
entries[].page | integer | Page number in the document |
entries[].snippet | string/null | Relevant text excerpt |
entries[].timestamp | float/null | Timestamp 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.
/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 Pattern | What 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/chat/{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:
| Plan | Max Templates (views) | Max Files per Template | Max Fields (Columns) per Template |
|---|---|---|---|
| Free | 1 | 10 | 10 |
| Agent | 1 | 10 | 10 |
| Pro | 2 | 10 | 10 |
| Business | 10 | 1000 | 50 |
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 400. When output= is omitted, responses are full and byte-for-byte unchanged.
Object metadata (GET /workspace/{id}/storage/{node}/metadata/details/):
| Level | Fields returned on each metadata record (cumulative) |
|---|---|
terse | object_id, template_id, node_id (narrowed to {id, name, type}), metadata (the full key/value payload) |
standard | terse + instance_id, node_id widened to {id, name, type, parent, mimetype}, autoextractable |
full | everything |
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/):
| Level | Fields returned (cumulative) |
|---|---|
terse | id, name, description, enabled, priority |
standard | terse + instanceId, orgId, locked, deleted, updated, created, fields, node_count, field_count, node_count_capped, plan_node_limit, is_truncated, plan_field_limit |
full | everything |
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/template/{tid}/resolve/views/):
| Level | Fields returned (cumulative) |
|---|---|
terse | name |
standard | terse + node_id, filters, order_by, order_desc, created, updated |
full | everything |
Eligible nodes (GET /workspace/{id}/metadata/eligible/):
| Level | Fields returned (cumulative) |
|---|---|
terse | node_id, name, mimetype |
standard | terse + size, summary_title, summary_short, updated, templates |
full | everything |
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
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Template name (1–255 characters) |
| description | string | Yes | Template description |
| category | string | Yes | Category (1–50 chars). Must be a valid category from the categories endpoint. |
| fields | string (JSON) | Yes | JSON-encoded array of field definitions |
Field definition structure:
| Property | Type | Description |
|---|---|---|
name | string | Field identifier (alphanumeric + underscore) |
description | string | Human-readable description |
type | string | string, int, float, bool, json, url, or datetime |
min | number | Minimum value/length constraint |
max | number | Maximum value/length constraint |
default | mixed | Default value |
fixed_list | array | Allowed values for dropdown-style fields |
can_be_null | boolean | Whether the field allows null values |
Request example:
curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/metadata/templates/" \
-H "Authorization: Bearer {jwt_token}" \
-d 'name=Invoice+Template' \
-d 'description=Metadata+schema+for+invoices' \
-d 'category=financial' \
-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": "yes",
"response": {
"template": {
"id": "mt_aBcDeFgHiJkLmN",
"instanceId": "12345678901234567890",
"orgId": "98765432109876543210",
"category": "financial",
"name": "Invoice Template",
"description": "Metadata schema for invoices",
"locked": false,
"priority": null,
"enabled": false,
"deleted": null,
"updated": "2025-01-20 10:30:00",
"created": "2025-01-20 10:30:00",
"fields": [
{
"name": "invoice_number",
"description": "Invoice number",
"type": "string",
"min": 1,
"max": 50,
"fixed_list": [],
"can_be_null": false
}
]
}
},
"current_api_version": "1.0"
}
Newly created templates start with enabled: false. Use the settings endpoint to enable them.
Delete a template
/current/workspace/{workspace_id}/metadata/templates/{template_id}/
Soft-delete a metadata template. System templates cannot be deleted.
Auth: Bearer token required. Workspace member. Metadata billing feature required.
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1683 (Resource Missing) | 404 | Template not found |
1680 (Access Denied) | 403 | Cannot delete a system template |
1664 (Datastore Error) | 500 | Deletion failed |
List templates
/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/12345678901234567890/metadata/templates/list/" \
-H "Authorization: Bearer {jwt_token}"
# List only enabled templates
curl -X GET "https://api.fast.io/current/workspace/12345678901234567890/metadata/templates/list/enabled/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"count": 2,
"items": [
{
"id": "mt_aBcDeFgHiJkLmN",
"instanceId": "12345678901234567890",
"orgId": "98765432109876543210",
"category": "legal",
"name": "Contract Template",
"description": "Standard contract metadata fields",
"locked": true,
"priority": 3,
"enabled": true,
"deleted": null,
"updated": "2025-01-15 08:00:00",
"created": "2025-01-01 00:00:00",
"fields": [
{
"name": "contract_type",
"description": "Type of contract",
"type": "string",
"fixed_list": ["NDA", "MSA", "SOW"],
"can_be_null": false
}
]
}
]
},
"current_api_version": "1.0"
}
Get template details
/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 settings
/current/workspace/{workspace_id}/metadata/templates/{template_id}/settings/
Update per-workspace instance settings (enable/disable, priority).
Auth: Bearer token required. Workspace member. Metadata billing feature required.
| Parameter | Type | Required | Description |
|---|---|---|---|
| enabled | string | Yes | "true" or "false" |
| priority | string | Yes | Priority level "1" through "5" |
Error responses:
| Error Code | HTTP Status | Cause |
|---|---|---|
1683 (Resource Missing) | 404 | Template not found |
1609 (Not Found) | 404 | Template is deleted |
1680 (Access Denied) | 403 | Template is locked |
1664 (Datastore Error) | 500 | Update failed |
Update template definition
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | Updated template name (1–255 characters) |
| description | string | No | Updated description (up to 1000 characters) |
| category | string | No | Updated category (1–50 characters) |
| fields | string (JSON) | No | Updated JSON-encoded array of field definitions |
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": "yes",
"response": {
"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
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Number of items to return (1–500, default: 100) |
| offset | integer | No | Number of items to skip (default: 0) |
Add nodes to template
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| node_ids | string (JSON) | Yes | JSON-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
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| node_ids | string (JSON) | Yes | JSON-encoded array of node IDs to remove from the template |
List nodes in template
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Number of items to return (1–500, default: 100) |
| offset | integer | No | Number of items to skip (default: 0) |
| sort_field | string | No | Optional template field name to sort by |
| sort_dir | string | No | asc or desc (only with sort_field) |
Response (additional fields):
| Field | Type | Description |
|---|---|---|
| total_count | integer | Visible row count under the per-plan cap |
| total_count_unfiltered | integer | True row count in storage (ignores cap) |
| plan_node_limit | integer | Per-template node cap for the workspace's plan; -1 indicates unlimited / unresolved |
| is_truncated | boolean | true 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
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Proposed template name (1–255 characters) |
| description | string | Yes | Proposed template description (1–2000 characters) |
Response (200 OK):
{
"result": "yes",
"response": {
"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
}
}
| Field | Type | Description |
|---|---|---|
| matched_files | array | Files that matched the proposed template |
| matched_files[].node_id | string | File node opaque ID |
| matched_files[].name | string | File name |
| matched_files[].mimetype | string | MIME type |
| matched_files[].summary_title | string | AI-generated summary title |
| matched_files[].summary_short | string | AI-generated short summary |
| total_eligible | integer | Size 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_scanned | integer | Files actually scanned in this preview. May be smaller than total_eligible when the sample is bounded |
| total_matched | integer | Files that matched |
| plan_node_limit | integer | Per-template node cap for the workspace's plan; -1 indicates unlimited / unresolved |
| would_truncate_at | integer | Number 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
/current/workspace/{workspace_id}/metadata/templates/suggest-fields/
Synchronously suggest a set of custom metadata fields for a proposed view, using AI. Accepts a small sample of eligible file node IDs (typically drawn from a prior preview-match call), a view 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 view-creation flow.
Auth: Bearer token required. Workspace member. Metadata billing feature required. Rate limited; concurrent calls per user+workspace return 409 Conflict.
| Parameter | Type | Required | Description |
|---|---|---|---|
| node_ids | string (JSON) | Yes | JSON-encoded array of file node IDs to sample (1–25 items) |
| description | string | Yes | View description (1–2000 characters) |
| user_context | string | No | Optional user intent string (max 64 characters, letters/numbers/spaces only) |
Response (200 OK):
{
"result": "yes",
"response": {
"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
}
}
| Field | Type | Description |
|---|---|---|
| suggested_fields | array | 1–10 suggested field definitions, directly compatible with create-template fields |
| suggested_fields[].name | string | Field display name |
| suggested_fields[].type | string | One of string, int, float, bool, datetime, url |
| suggested_fields[].description | string | Human-readable field description |
| suggested_fields[].max | number | Maximum length/value constraint (when applicable) |
| suggested_fields[].can_be_null | bool | Whether the field may be null |
| suggested_fields[].fixed_list | array | Allowed values for bounded categorical fields (status, priority, category). Present only when the field is enumerable. |
| suggested_fields[].example_value | mixed | Display-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_count | integer | Number 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
/current/workspace/{workspace_id}/metadata/templates/{template_id}/auto-match/
Use AI to automatically match eligible files in the workspace to the template based on file content and template field definitions. Progress is tracked via the jobs status endpoint under the template_match key.
Auth: Bearer token required. Workspace member. Metadata billing feature required.
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)
/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": "yes",
"response": {
"job_id": "aj_aBcDeFgHiJkLmN",
"template_id": "mt_oPqRsTuVwXyZ12"
}
}
Maximum 1,000 files processed per job.
Node Metadata
Metadata stored on individual files. Split into template metadata (conforming to mapped templates) and custom metadata (user-defined fields).
Get file metadata
/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/12345678901234567890/storage/aBcDeFgHiJkLmN/metadata/details/" \
-H "Authorization: Bearer {jwt_token}"
Response (200 OK):
{
"result": "yes",
"response": {
"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"
},
{
"key": "amount",
"description": "Invoice amount",
"type": "float",
"value": 1500.00,
"is_auto": true,
"updated": "2025-01-20 10:30:00"
}
]
},
"current_api_version": "1.0"
}
| Field | Type | Description |
|---|---|---|
response.template_id | string/null | Associated template ID, or null |
response.node_id | object | Storage node resource with file details |
response.metadata[].key | string | Metadata field key |
response.metadata[].type | string | Value type (string, int, float, bool, json, url, datetime) |
response.metadata[].value | mixed | The metadata value |
response.metadata[].is_auto | boolean | Whether the value was auto-generated by AI extraction |
response.metadata[].updated | string | Last update timestamp |
Update file metadata
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| key_values | string (JSON) | Yes | JSON-encoded object of key-value pairs. Keys must match template field names. |
Request example:
curl -X POST "https://api.fast.io/current/workspace/12345678901234567890/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
/current/workspace/{workspace_id}/storage/{node_id}/metadata/
Delete metadata keys from a file.
Auth: Bearer token required. Workspace member. Metadata billing feature required.
| Parameter | Type | Required | Description |
|---|---|---|---|
| keys | string (JSON) | No | JSON-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)
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| template_id | string | No | The template ID to extract against. Defaults to the first template mapped to the file. |
| fields | string (JSON) | No | JSON-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": "yes",
"response": {
"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 Code | HTTP Status | Cause |
|---|---|---|
1605 (Invalid Input) | 400 | Node is root, fields references an unknown field, or payload is malformed |
1609 (Not Found) | 404 | Template not found |
1609 (Not Found) | 404 | Node not found |
1664 (Datastore Error) | 500 | Failed to enqueue extraction job |
1696 (Credits Exhausted) | 402 | No AI credits remaining |
List metadata by template
/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| filters | string (JSON) | No | JSON-encoded filter criteria |
| order_by | string | No | Field key name to sort by |
| order_desc | string | No | "true" or "false" for descending sort |
List templates in use
/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
/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.
/current/workspace/{workspace_id}/metadata/view/?template_id={template_id}
/current/workspace/{workspace_id}/metadata/view/
/current/workspace/{workspace_id}/metadata/view/?template_id={template_id}
/current/workspace/{workspace_id}/metadata/views/
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.
Template Resource Schema
| Field | Type | Description |
|---|---|---|
id | string | Opaque template identifier |
instanceId | string | Workspace ID |
orgId | string/null | Organization ID |
category | string | Template category |
name | string | Template name |
description | string | Template description |
locked | boolean | Whether the template is locked from editing |
priority | integer/null | Priority level 1–5, or null |
enabled | boolean | Whether enabled for the workspace |
deleted | string/null | Soft-delete timestamp, or null |
updated | string | Last updated timestamp |
created | string | Creation timestamp |
fields | array | Array of field definition objects |
Supported Field Types
| Type | Description |
|---|---|
string | Text values (max 10,000 characters) |
int | Integer numbers |
float | Decimal numbers |
bool | Boolean true/false |
json | Complex JSON structures |
url | Validated URLs |
datetime | Date and time values |
Quick Reference
Create a RAG chat and get the answer
POST /current/workspace/{id}/ai/chat/
question=...&type=chat_with_files&personality=detailed
-> chat_id, message_id
GET /current/activity/poll/{id}?wait=95&lastactivity=...
-> wait for ai_chat:{chatId}
GET /current/workspace/{id}/ai/chat/{chat_id}/message/{msg_id}/details/
-> check state == complete
GET /current/workspace/{id}/ai/chat/{chat_id}/message/{msg_id}/read/
-> SSE stream: data, 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: 2/min, 10/hr)
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.