// Features

Document Workspace

Files, memories, and artifacts indexed together under one scoping boundary. Upload a PDF or paste a transcript, then let the model search it from any chat assigned to that workspace via a single tool call.

Unlike chat-level document uploads (which are scoped to a single session), workspace documents persist across all chats within the workspace. Any chat that is assigned to a workspace can search across all workspace files, memories, and artifacts in a single query.

Workspaces are separate from chat sessions. Every project has a default workspace. Additional workspaces can be created to isolate different knowledge domains.

For more information about how memories interact with workspaces, see the Chat Memory documentation. For a visual representation of how workspaces, chats, and documents relate to each other, see the Workspace Graph.

Key Capabilities

  • Workspace CRUD: create, list, get, update, delete workspaces.
  • File upload with automatic text extraction, chunking, and embedding.
  • Unified semantic search across memories, documents, artifacts, decisions, and milestones via x_workspace_search.
  • Workspace-level memory, decision, milestone, member, and preference management.
  • REST search API for programmatic access to workspace knowledge.

ID Forms

Examples on this page use the following identifier prefixes:

  • ws_xxx, workspace id (operator-facing external form).
  • cart_xxx, artifact id (uploaded files and generated artifacts); see Artifacts.
  • mem_xxx, workspace or chat memory id; see Chat Memory.

API Surfaces

Workspace endpoints are exposed on two mounts with different authentication models and slightly different response shapes. Read this section before copying request examples from elsewhere in the page.

Surface Mount Auth Notes
Dashboard backend /chat/api/workspaces Session cookie Used by the chat UI. Examples on this page use this surface.
Router public API /{projectId}/v1/workspaces Bearer API key OpenAI-compatible surface. Returns the wide router DTOs described below; field names diverge from the dashboard wrapper DTOs.

Dual-shape warning: The dashboard upload endpoint returns a wrapper DTO with content_type and bytes; the router-side file list returns the wide DTO with mime_type, size_bytes, content_hash, checksum, storage_location, and deleted_at for the same underlying row. The same divergence applies to workspace and memory records. Examples on this page reflect the dashboard wrapper unless otherwise noted.

Endpoint Matrix

Thirteen endpoints on this surface, jump straight to one:

Workspace API

All workspace endpoints are mounted under /chat/api/workspaces and require an authenticated session with a valid project context. Workspace IDs use the ws_xxx format.

Create Workspace

POST /chat/api/workspaces

Creates a new workspace. Name must be 255 characters or fewer; description must be 2000 characters or fewer.

Request Body

FieldTypeRequiredDescription
namerequired string Yes Display name for the workspace.
descriptionoptional string No Optional description for the workspace.
curl
curl -X POST https://api.xerotier.ai/chat/api/workspaces \ -H "Cookie: session=your_session_token" \ -H "Content-Type: application/json" \ -d '{ "name": "Research Papers", "description": "Uploaded PDFs for literature review" }'

Response

The dashboard wrapper returns the abbreviated shape shown below (see dual-shape warning). The router-side wide DTO additionally emits project_id, kind, visibility, agentic_by_default, allow_mcp_exec, memory_extraction_auto_failover, project_intelligence_enabled, updated_at, and deleted_at. Treat any field not listed below as router-only.

JSON
{ "id": "ws_abc123", "name": "Research Papers", "description": "Uploaded PDFs for literature review", "is_default": false, "chat_count": 0, "created_at": "2026-04-09T10:00:00Z" }

List Workspaces

GET /chat/api/workspaces

Returns all active workspaces for the current user within the project. Results are ordered with the default workspace first, then alphabetically by name. Each entry includes a chat_count for the number of chats assigned to that workspace.

curl
curl https://api.xerotier.ai/chat/api/workspaces \ -H "Cookie: session=your_session_token"

Response

JSON
[ { "id": "ws_default123", "name": "Default", "description": null, "is_default": true, "chat_count": 12, "created_at": "2026-01-01T00:00:00Z" }, { "id": "ws_abc123", "name": "Research Papers", "description": "Uploaded PDFs for literature review", "is_default": false, "chat_count": 3, "created_at": "2026-04-09T10:00:00Z" } ]

Get Workspace

GET /chat/api/workspaces/{workspaceId}

Returns detailed workspace information including resource usage statistics. The router-side wide DTO additionally emits the same router-only fields listed under Create Workspace.

Percent fields: storage_used_percentage and embedding_used_percentage are computed against the resolved quota. They report 0.0 when the workspace inherits the project tier default (no per-workspace override) or when the configured limit is unlimited; a zero value does not mean the workspace is empty.

curl
curl https://api.xerotier.ai/chat/api/workspaces/ws_abc123 \ -H "Cookie: session=your_session_token"

Response

JSON
{ "id": "ws_abc123", "name": "Research Papers", "description": "Uploaded PDFs for literature review", "is_default": false, "chat_count": 3, "created_at": "2026-04-09T10:00:00Z", "stats": { "total_files": 5, "total_file_bytes": 10485760, "total_memories": 23, "total_embeddings": 412, "storage_limit_bytes": 104857600, "embedding_limit_count": 10000, "storage_used_percentage": 10.0, "embedding_used_percentage": 4.12 } }

Update Workspace

PATCH /chat/api/workspaces/{workspaceId}

Updates a workspace's name and/or description. Returns HTTP 204 No Content on success.

curl
curl -X PATCH https://api.xerotier.ai/chat/api/workspaces/ws_abc123 \ -H "Cookie: session=your_session_token" \ -H "Content-Type: application/json" \ -d '{"name": "Literature Review", "description": "Updated description"}'

Delete Workspace

DELETE /chat/api/workspaces/{workspaceId}

Soft-deletes a workspace. The default workspace cannot be deleted and returns a 400 error if attempted. Returns HTTP 204 No Content on success.

curl
curl -X DELETE https://api.xerotier.ai/chat/api/workspaces/ws_abc123 \ -H "Cookie: session=your_session_token"

Supported File Types

The following file types are supported for upload. Text-based formats are fully searchable after processing. Binary document formats are processed through extractors to produce searchable text.

Note: Only text-based files and recognized binary document formats are accepted. Files that fail UTF-8 validation and do not have a recognized binary extension are rejected at upload time.

Type Extensions Extraction Method
PDF .pdf Text extracted via PDF extractor
HTML .html, .htm Text extracted via HTML extractor
Word documents .docx, .doc, .rtf, .odt Text extracted via document extractor
Presentations .pptx Text extracted via document extractor
Spreadsheets .xlsx, .ods Text extracted via document extractor
E-books .epub Text extracted via document extractor
Plain text .txt, .md, .csv, .json, .xml, .yaml, .toml Raw content used directly
Code files .swift, .py, .js, .ts, .go, .rs, .java, .c, .cpp, .rb, .sh, .sql, etc. Raw content used directly
Config files .env, .ini, .cfg, .conf, .log, Dockerfile, Makefile Raw content used directly

Upload Flow

When a file is uploaded to a workspace, the following processing pipeline executes:

  1. File validation, The file's MIME type is determined from its extension. Unknown extensions are accepted only if the raw content is valid UTF-8 text; binary files without recognized extensions are rejected. For known binary extensions, an additional magic-prefix check verifies that the file header matches the declared extension, which blocks rename attacks (e.g. an .exe renamed to .pdf).
  2. Storage quota check, The workspace's storage quota is checked before accepting the upload. If the upload would exceed the quota, the request is rejected with a 400 error.
  3. Object storage, The file is stored in object storage under a workspace-scoped path. An artifact record is created in the database linking the file to the workspace.
  4. Text extraction, The appropriate extractor runs based on file type (PDF extractor, HTML extractor, or raw content pass-through for text and code files).
  5. Chunking, Extracted text is split into structure-aware chunks of approximately 450 tokens (estimated at ~4 characters per token) with 64 tokens of overlap between adjacent chunks. The splitter walks paragraph boundaries first, falls back to sentence boundaries when a single paragraph is too long, and only hard-wraps text as a last resort, which preserves semantic coherence at chunk boundaries. The 450-token target keeps each chunk within the effective input window of common sentence-transformer embedding models so the embedding endpoint never silently truncates content.
  6. Embedding, Each chunk is embedded as a vector representation for semantic search. The dimensionality is determined by the configured embedding model.
  7. Available for search, The document and its chunks are immediately available for search by the model via the file_search and x_workspace_search tools.

Note: Processing happens asynchronously after upload. The upload endpoint returns immediately with the artifact record; chunking and embedding run in a background task. Small text files are typically searchable within seconds. Larger documents may take longer depending on content size.

file_search Tool (client-passthrough)

Not a server-implemented tool. file_search is advertised by the AgenticLoop chat tool tier as a client-passthrough tool: the router does not execute it, owns no schema for it, and emits no server-side events for it. There is no response.file_search_call.* SSE event family in the router today.

The chat tool tier advertises file_search so that client SDKs (e.g. an OpenAI Responses-API client wrapping this router) can declare it and dispatch the call locally. Anything you need to do against workspace-stored documents from the server side should use x_workspace_search below or the Unified Search REST endpoint.

x_workspace_search Tool

The x_workspace_search tool performs unified semantic search across all content types within the workspace, memories, document chunks, and artifacts. It is available to the model whenever a chat is assigned to a workspace.

Unlike file_search, which searches only document chunks within a single chat, x_workspace_search spans the entire workspace and returns ranked results from all three content types in a single call.

Parameters

Parameter Type Description
queryrequired string Free-text search string across indexed workspace content (500 character maximum).
content_typesoptional string[] Restrict to specific content types. Any of: memories, documents, artifacts, decisions, milestones. Defaults to all when omitted.
offsetoptional integer Zero-based row offset for pagination. Default: 0.
limitoptional integer Max hits to return. Default: 5. Capped at 20.

Return Value

The tool returns a markdown string, it does NOT return a JSON array. Output starts with a method note such as _method: pgvector_ (or _method: ilike, embeddings unavailable_) followed by a ranked, human-readable list of hits. Each entry surfaces the content kind, source identifier, snippet, score, and creation timestamp. Pass the string back to the model verbatim; do not JSON.parse it.

Graceful Degradation

The retriever combines pgvector similarity with an ILIKE substring filter over the same rows; the ILIKE branch is reached through the combinator, not as a standalone replacement. When the workspace has no embeddings endpoint configured at all, the retriever returns no hits and the tool emits a markdown block with an empty result list and the method note set accordingly, rather than raising an error.

Example Tool Call

JSON
{ "name": "x_workspace_search", "arguments": { "query": "database sharding and partition strategies", "content_types": ["documents", "memories"], "limit": 5 } }

Example Response (markdown)

text/markdown
_method: pgvector_ # Workspace search results: database sharding and partition strategies 1. document, architecture-overview.pdf (score 0.92) Database sharding strategy follows a hash-based partitioning scheme across four primary nodes... - kind: document - created_at: 2026-04-09T10:00:00Z 2. memory, Architecture Review Chat (score 0.86) The team decided on a range-based sharding key for the users table in Q1 2026. - kind: memory - created_at: 2026-03-15T14:22:00Z offset=0 limit=5 has_more=false

Workspace File API

REST endpoints for managing files uploaded to a workspace. All endpoints require authentication and operate within the scope of a specific workspace.

Upload File

POST /chat/api/workspaces/{workspaceId}/upload

Uploads a file to the workspace using multipart/form-data. The file is stored, and background document processing (text extraction, chunking, embedding) is triggered automatically. Returns the created file DTO with HTTP 201 Created.

curl
curl -X POST https://api.xerotier.ai/chat/api/workspaces/ws_abc123/upload \ -H "Cookie: session=your_session_token" \ -F "file=@architecture-overview.pdf"

Response

Dashboard wrapper shape shown below (see dual-shape warning). bytes reports the source payload size as received; if encryption-at-rest is enabled the stored blob may differ. The router-side wide DTO substitutes mime_type + size_bytes (stored-blob size) and adds workspace_id, content_hash, checksum, storage_location, and deleted_at.

JSON
{ "id": "cart_xyz789", "name": "architecture-overview.pdf", "content_type": "application/pdf", "bytes": 2048576, "created_at": "2026-04-09T10:05:00Z" }

List Files

GET /chat/api/workspaces/{workspaceId}/files

Returns uploaded files within the workspace with cursor-based pagination. Results are sorted by creation date descending (most recent first). Accepts optional cursor and limit query parameters.

curl
curl "https://api.xerotier.ai/chat/api/workspaces/ws_abc123/files?limit=20" \ -H "Cookie: session=your_session_token"

Response

JSON
{ "object": "list", "data": [ { "id": "cart_xyz789", "name": "architecture-overview.pdf", "content_type": "application/pdf", "bytes": 2048576, "created_at": "2026-04-09T10:05:00Z" }, { "id": "cart_uvw456", "name": "design-notes.md", "content_type": "text/markdown", "bytes": 15234, "created_at": "2026-04-09T10:00:00Z" } ], "next_cursor": "eyJkYXRlIjoiMjAyNi0wNC0wOV...", "has_more": false }

Delete File

DELETE /chat/api/workspaces/{workspaceId}/files/{artifactId}

Soft-deletes an uploaded file within the workspace. The artifact record is marked with a deleted_at timestamp and excluded from future queries and search results. Returns HTTP 204 No Content on success.

curl
curl -X DELETE https://api.xerotier.ai/chat/api/workspaces/ws_abc123/files/cart_xyz789 \ -H "Cookie: session=your_session_token"

Python (requests)

Python
import requests base = "https://api.xerotier.ai/chat/api/workspaces" workspace_id = "ws_abc123" cookies = {"session": "your_session_token"} # Upload a file with open("architecture-overview.pdf", "rb") as f: resp = requests.post( f"{base}/{workspace_id}/upload", cookies=cookies, files={"file": ("architecture-overview.pdf", f, "application/pdf")} ) artifact_id = resp.json()["id"] # List files (first page) page = requests.get( f"{base}/{workspace_id}/files", cookies=cookies, params={"limit": 20} ).json() for f in page["data"]: print(f"{f['name']} ({f['bytes']} bytes)") # Delete a file requests.delete(f"{base}/{workspace_id}/files/{artifact_id}", cookies=cookies)

Node.js (fetch)

JavaScript
const base = "https://api.xerotier.ai/chat/api/workspaces"; const workspaceId = "ws_abc123"; const sessionHeaders = { "Cookie": "session=your_session_token" }; // Upload a file const form = new FormData(); form.append("file", new Blob([fileBuffer], { type: "application/pdf" }), "architecture-overview.pdf"); const uploadRes = await fetch(`${base}/${workspaceId}/upload`, { method: "POST", headers: sessionHeaders, body: form }); const { id: artifactId } = await uploadRes.json(); // List files const listRes = await fetch(`${base}/${workspaceId}/files?limit=20`, { headers: sessionHeaders }); const page = await listRes.json(); page.data.forEach(f => console.log(`${f.name} (${f.bytes} bytes)`)); // Delete a file await fetch(`${base}/${workspaceId}/files/${artifactId}`, { method: "DELETE", headers: sessionHeaders });

Workspace-Scoped Memories

Memories generated within chats that belong to a workspace are scoped to that workspace and included in x_workspace_search results. This allows the model to retrieve relevant knowledge from past conversations across the entire workspace, not just the current chat.

Every chat is assigned to a workspace at creation time. Chats without an explicit workspace are routed to the project's default workspace at creation, so every memory is always scoped to a workspace. Workspace memory management is available via the following endpoints. For chat-level memory operations and the x_memory tool, see Chat Memory.

Create Workspace Memory

POST /chat/api/workspaces/{workspaceId}/memories

Creates a memory directly against the workspace (no source chat). Content must be 2000 characters or fewer.

curl
curl -X POST "https://api.xerotier.ai/chat/api/workspaces/ws_abc123/memories" \ -H "Cookie: session=your_session_token" \ -H "Content-Type: application/json" \ -d '{"content": "Production uses postgres 16 with pgvector 0.7"}'

List Workspace Memories

GET /chat/api/workspaces/{workspaceId}/memories

Returns non-deleted memories scoped to the workspace, sorted by creation date descending. Accepts optional cursor and limit query parameters.

curl
curl "https://api.xerotier.ai/chat/api/workspaces/ws_abc123/memories?limit=20" \ -H "Cookie: session=your_session_token"

Response

Dashboard wrapper shape shown below (see dual-shape warning). The router-side wide memory DTO additionally emits workspace_id, source_chat_id, and source_message_id when the memory originated from a chat.

JSON
{ "items": [ { "id": "mem_abc123", "content": "The team decided on a range-based sharding key for the users table", "summary": "Team chose range-based sharding for users", "category": "fact", "source_type": "model", "created_at": "2026-03-15T14:22:00Z" } ], "next_cursor": null, "has_more": false }

Update Workspace Memory

PATCH /chat/api/workspaces/{workspaceId}/memories/{memoryId}

Updates memory content. Content must be 2000 characters or fewer.

curl
curl -X PATCH "https://api.xerotier.ai/chat/api/workspaces/ws_abc123/memories/mem_abc123" \ -H "Cookie: session=your_session_token" \ -H "Content-Type: application/json" \ -d '{"content": "Updated memory content"}'

Delete Workspace Memory

DELETE /chat/api/workspaces/{workspaceId}/memories/{memoryId}

Soft-deletes a memory from the workspace. Returns HTTP 204 No Content.

curl
curl -X DELETE "https://api.xerotier.ai/chat/api/workspaces/ws_abc123/memories/mem_abc123" \ -H "Cookie: session=your_session_token"

Knowledge, Members, and Preferences

In addition to file, memory, and search routes, the dashboard mount exposes additional management endpoints used by the knowledge tab and the workspace settings UI. The table below lists the endpoints mounted under /chat/api/workspaces/{workspaceId}.

Method Path suffix Purpose
PATCH /visibility Update workspace visibility (private / project / public-within-tenant).
GET /members List members granted access to the workspace.
POST /members Add a member.
DELETE /members/{userId} Remove a member.
GET /decisions List tracked decisions.
PATCH /decisions/{decisionId} Update a tracked decision.
DELETE /decisions/{decisionId} Soft-delete a tracked decision.
GET /milestones List tracked milestones.
PATCH /milestones/{milestoneId} Update a tracked milestone.
DELETE /milestones/{milestoneId} Soft-delete a tracked milestone.
DELETE /relations/{relationId} Remove a relation edge between knowledge entities.
POST /revalidate-memories Re-embed workspace memories against the current embeddings model.
POST /revalidate-embeddings Re-embed all workspace content (documents, memories, artifacts).
GET /preferences Read workspace preferences (reranking, agentic defaults, etc.).
PATCH /preferences Update workspace preferences.

For a canonical schema for each tracked-knowledge resource, see Server Tools (which documents the x_workspace_search tool that indexes them) and Workspace Graph for the relation model.

Unified Search API

The search endpoint exposes the same underlying search used by the x_workspace_search tool, allowing programmatic access to workspace knowledge from outside a chat session.

Search Workspace

POST /chat/api/workspaces/{workspaceId}/search

Generates an embedding for the query text and searches memories, document chunks, artifacts, decisions, and milestones within the workspace. Results are sorted by cosine similarity (most relevant first). Duplicate results with identical content are removed server-side before the response is returned. max_results is silently clamped to the service default when omitted (default 10) and to the service cap when oversized; callers should not rely on receiving exactly the requested count.

Request Body

FieldTypeRequiredDescription
queryrequired string Yes Search query text (500 character maximum).
max_resultsoptional integer No Maximum number of results to return. Default: 10.
content_typesoptional string[] No Filter to specific content types. Accepted values: "memories", "documents", "artifacts", "decisions", "milestones". When omitted, all types are searched.
curl
curl -X POST https://api.xerotier.ai/chat/api/workspaces/ws_abc123/search \ -H "Cookie: session=your_session_token" \ -H "Content-Type: application/json" \ -d '{ "query": "database sharding strategies", "max_results": 5, "content_types": ["documents", "memories"] }'

Response

JSON
[ { "type": "document", "content": "Database sharding strategy follows a hash-based partitioning scheme across four primary nodes...", "source": "architecture-overview.pdf", "distance": 0.08, "created_at": "2026-04-09T10:00:00Z", "metadata": { "chunk_index": "3", "artifact_id": "cart_abc123" } }, { "type": "memory", "content": "The team decided on range-based sharding for the users table", "source": "Architecture Review Chat", "distance": 0.14, "created_at": "2026-03-15T14:22:00Z", "metadata": { "category": "fact", "memory_id": "mem_def456" } } ]

Python (requests)

Python
import requests base = "https://api.xerotier.ai/chat/api/workspaces" workspace_id = "ws_abc123" cookies = {"session": "your_session_token"} results = requests.post( f"{base}/{workspace_id}/search", cookies=cookies, json={ "query": "database sharding strategies", "max_results": 5, "content_types": ["documents", "memories"] } ).json() for r in results: print(f"[{r['type']}] {r['source']} (distance={r['distance']:.3f})") print(f" {r['content'][:120]}...")

Chat UI

The chat interface includes a documents sidebar panel for managing uploaded files within the current chat session. A separate workspace management view is available from the workspace selector.

  • Upload area, A drag-and-drop zone for uploading files. Users can drag files from their desktop directly into the panel, or click to open a file picker dialog.
  • Document list, Displays all uploaded documents with their filename, file size, and file type. Documents are listed in upload order with the most recent at the top.
  • Delete button, Each document row includes a delete button that removes the document from the workspace after confirmation.
  • Document count badge, The toolbar displays a badge showing the total number of documents in the workspace. The badge updates in real time as documents are added or removed.

The dashboard UI shows an upload-time spinner for the in-flight request only. Background processing (extraction, chunking, embedding) runs asynchronously after the upload returns and is not currently surfaced as a per-document status indicator on the file list response, so refreshing the list shortly after upload may show a document that is not yet searchable.

For a visual overview of how workspaces, chats, documents, and memories are connected, see the Workspace Graph visualization.

Limits

The following limits apply to workspace file uploads and resource usage. Storage and embedding limits are configurable per workspace by the platform administrator; workspace-level overrides take precedence over tier defaults.

Limit Default Description
Maximum file size per upload 10 MB Maximum size of a single uploaded file. Files exceeding this limit are rejected at upload time with a 400 error.
Storage limit per workspace Tier default Total bytes allowed across all files in the workspace. Configurable per workspace. When nil, the project tier default applies.
Embedding vector limit Tier default Maximum number of embedding vectors allowed in the workspace (memories + document chunks + artifacts). When nil, the tier default applies.
Chunk size ~450 tokens Each document is split into structure-aware chunks of approximately 450 tokens (estimated at ~4 characters per token) with 64-token overlap between adjacent chunks. The splitter respects paragraph and sentence boundaries before falling back to character wrapping. The size leaves headroom for the 512-token effective input window of common sentence-transformer embedding models.
Search query length 500 characters Maximum length of a search query submitted to the workspace search endpoint or x_workspace_search tool.
Workspace name length 255 characters Maximum length of a workspace display name.
Workspace description length 2000 characters Maximum length of a workspace description.

Note: When the workspace storage quota would be exceeded by an upload, the request is rejected before the file is stored. The quota check uses the raw file size in bytes; compression or extraction results do not affect quota accounting.

Common Errors

Status Trigger Recovery
400 Upload would exceed workspace storage quota. Delete unused files, raise the per-workspace override, or upload to a different workspace.
400 Attempt to delete the default workspace. Create another workspace, move chats, then delete the non-default one.
400 Unknown extension and non-UTF-8 payload at upload time. Pre-convert to a supported text or document format from the list above.
400 Magic-prefix mismatch (file renamed across binary types). Re-export the source in its native format; do not rename across extensions.
404 Workspace, file, or memory id not visible to the caller. Verify the id form (ws_, cart_, mem_) and project context.