# Fastio for AI Agents

> **Version:** 1.35.0 | **Last updated:** 2026-07-07
>
> This guide is available at the `/current/agents/` endpoint on the connected API server.

**Put your files to work — workspaces for agentic teams.** Fastio answers across your files, automates the busywork, and keeps work secure and on the record, for your people and your agents, all through one API. Storage is step zero.

Fastio provides workspaces for agentic teams — where agents collaborate with other agents and with humans. Upload
outputs, create branded portals, ask questions about documents using built-in AI, and collaborate with humans on a
shared platform. No infrastructure to manage — Fastio accounts, for humans and AI agents alike, require an email
address: sign up, create an organization, and choose a paid plan to get started.

The platform is organized around two things you do with your files: **Intelligence** — ask across them and get cited answers, turn documents and images into structured data (Metadata Views), and research, analyze, and draft with the built-in agent, **Ripley**; and **Secure collaboration** — files belong to the project, not the person, with granular permissions, scoped agent tokens, branded portals, and an append-only audit log. Storage is step zero; Fastio provides the rest.

There are three ways to integrate with Fastio:

| Integration | Best For | Get Started |
|-------------|----------|-------------|
| **CLI** | Terminal workflows, scripting, CI/CD pipelines, human operators | `npm install -g @vividengine/fastio-cli` |
| **MCP Server** | AI agents (Claude Desktop, Claude Code, Cursor, etc.) | Connect to `https://mcp.fast.io/mcp` |
| **REST API** | Custom applications, languages without MCP support | See API endpoints throughout this guide |

**CLI** — The `fastio` command-line tool provides full platform access from the terminal. Install with
`npm install -g @vividengine/fastio-cli` and authenticate with `fastio auth login`. The CLI also includes a built-in
MCP server mode (`fastio mcp`) for local AI agent integration. See the "CLI Tool" section below for full details.

**MCP-enabled agents** should connect via the Model Context Protocol for the simplest integration — no raw HTTP calls
needed.

**MCP connection endpoints:**
- **Streamable HTTP (recommended):** `https://mcp.fast.io/mcp`
- **Legacy SSE:** `https://mcp.fast.io/sse`

The MCP server exposes consolidated tools using action-based routing — each tool covers a domain (e.g., `auth`,
`storage`, `upload`) and uses an `action` parameter to select the operation. In Named Mode (Claude Desktop, etc.),
there are multiple domain-specific tools plus app-specific widget tools. In Code Mode (Claude Code,
Cursor, etc.), there is a smaller set of streamlined tools. See the "MCP Tool Architecture" section
below for the full tool list.

MCP-connected agents receive comprehensive workflow guidance through SERVER_INSTRUCTIONS at connection time, and can
read resources (`resources/read`) including `skill://guide` for full tool documentation, `session://status` for current
authentication state, and `download://` resource templates for direct file content retrieval.

This guide covers platform concepts and capabilities; the MCP server provides tool-level details through its standard
protocol interface. The API endpoints referenced below are what the MCP server calls under the hood, and are available
for agents that need direct HTTP access or capabilities not yet covered by the MCP tools.

---

## Why Agents Use Fastio

### The Problem

Agentic teams — groups of agents working together and with humans — need a shared place to work. Today, agents cobble
together S3 buckets, presigned URLs, email attachments, and custom download pages. Every agent reinvents collaboration,
and there's no shared workspace where agents and humans can see the same files, track activity, and hand off work.

Meanwhile, when agents need to *understand* documents — not just store them — they have to download files, parse dozens
of formats, build search indexes, and manage their own RAG pipeline. That's a lot of infrastructure for what should be a
simple question: "What does this document say?"

### What Fastio Solves

| Problem                                      | Fastio Solution                                                                                  |
|----------------------------------------------|---------------------------------------------------------------------------------------------------|
| No shared workspace for agentic teams        | Workspaces where agents and humans collaborate with file preview, versioning, and AI              |
| Agent-to-agent coordination lacks structure  | Shared workspaces with activity feeds, comments, and real-time sync across team members           |
| Sharing outputs with humans is awkward       | Purpose-built shares (Send, Receive, Exchange) with link sharing, passwords, expiration           |
| Collecting files from humans is harder       | Receive shares let humans upload directly to your workspace — no email attachments                |
| Understanding document contents              | Built-in AI reads, summarizes, and answers questions about your documents and code (agentic chat and intelligence indexing require a paid plan — Starter, Business, or Growth) |
| Building a RAG pipeline from scratch         | Enable intelligence on a workspace and documents are automatically indexed, summarized, and queryable (requires a paid plan) |
| Finding the right file in a large collection | Semantic search finds documents by meaning, not just filename                                     |
| Getting documents signed                     | Native e-signature: assemble an envelope, send with OTP identity checks, and the executed PDF + audit certificate file back into the workspace |
| Turning unstructured files into data         | AI metadata extraction (Metadata Views) pulls typed fields from documents, images, and spreadsheets into a sortable table |
| Files walking out when someone leaves        | Files belong to the org/workspace (the project), not the uploader — access follows the work, not the person |
| Knowing what needs attention                 | A per-workspace dashboard ranks @mentions and file activity |
| Collaborating with humans on a shared org    | Invite humans (or be invited) as org/workspace members — everyone sees the same files and activity |
| Tracking what happened                       | Full audit trail with AI-powered activity summaries                                               |
| Plans                                        | New organizations choose a paid plan (Starter, Business, or Growth); credits cover storage, bandwidth, and AI usage |

---

## Getting Started — Choosing the Right Access Pattern

Fastio accounts — for humans and AI agents alike — require an email address. There is one kind of account; an agent
account is an ordinary account tagged `account_type=agent` for identification. The getting-started flow is the same for
everyone: sign up, create an organization, choose a paid plan, then build. There are a few access patterns depending on
whether you're operating autonomously or working inside a human's existing organization.

### Option 1: Autonomous Agent — Create Your Own Account

If you're operating as part of an agentic team (collaborating with other agents, sharing outputs with humans,
coordinating shared work), create your own account and your own organization:

1. `POST /current/user/` with `email_address`, `password`, `tos_agree=true`. Optionally pass `agent=true` to tag the
   account as an agent account (`account_type=agent`) — this is for identification only and does not change which plans
   you can choose.
2. Authenticate with Basic Auth → get JWT
3. Verify your email address (required before using most endpoints):
   - `POST /current/user/email/validate/` with `email` — sends a verification code to your email
   - `POST /current/user/email/validate/` with `email` and `email_token` — validates the code and marks your account as verified
4. `POST /current/org/create/` with `domain` (required, 2-80 chars lowercase alphanumeric + hyphens) — an org is a collector of workspaces that can represent a company, team, business unit, or personal collection
5. **Select a paid plan to activate the organization.** A newly created organization must select a paid plan
   (Starter, Business, or Growth) before it can be used; until then it is in an upgrade-only state — the same state as an
   org that has exhausted its credits, returning HTTP 402 on resource-consuming endpoints. Choose a plan via the
   billing API or direct the owner to `https://go.fast.io/onboarding`.
6. `POST /current/org/{org_id}/create/workspace/` with `folder_name`, `name`, `perm_join`, `perm_member_manage` (all required — see Permission Values below)

> **A new organization needs a paid plan before it can do work.** New organizations select one of the paid plans
> (Starter, Business, or Growth). Until a paid plan is selected, the org is in an upgrade-only state and resource-consuming
> endpoints (uploads, AI chat, ingestion) return HTTP 402, exactly like an org that has run out of credits. All paid
> plans include the `content_ai` and `ai_agent` features needed for agentic chat and RAG across indexed files.
> The [AI reference](https://api.fast.io/current/llms/ai/#plan-requirements) has the full plan matrix.

#### Permission Values

Workspace and share creation require human-readable permission strings:

**Workspace `perm_join`** (who can auto-join from the org):
- `'Member or above'` (default) — any org member can join
- `'Admin or above'` — only org admins and owners
- `'Only Org Owners'` — only org owners

**Workspace `perm_member_manage`** (who can manage workspace members):
- `'Member or above'` (default) — any workspace member can manage
- `'Admin or above'` — only workspace admins and owners

**Share `access_options`** (who can access the share):
- `'Only members of the Share or Workspace'` (default)
- `'Members of the Share, Workspace or Org'`
- `'Anyone with a registered account'`
- `'Anyone with the link'` (allows password protection)

### Option 2: Assisting a Human — Use Their API Key

If a human already has a Fastio account and wants your help managing their files, workspaces, or shares, they can
create an API key for you to use. No separate agent account is needed — you operate as the human user.

**How the human creates an API key:**

Go to **Settings → Devices & Agents → API Keys** and click **Create API Key**. Optionally enter a memo to label the
key (e.g., "CI pipeline" or "Agent access"), then click **Create**. Copy the key immediately — it is only displayed
once and cannot be retrieved later. Direct link: `https://go.fast.io/settings/api-keys`

Use the API key as a Bearer token: `Authorization: Bearer {api_key}`

The API key has the same permissions as the human user, so you can manage their workspaces, shares, and files directly.

### Option 3: Agent Account Invited to a Human's Org

If you want your own agent identity but need to work within a human's existing organization (their company, team, or personal collection), you can create an agent account and have the human invite you as a member. This gives you access to their workspaces and shares while keeping your own account separate.

**How the human invites the agent to their org:**

Go to **Settings → Your Organization → [Org Name] → Manage People** and click **Invite People**. Enter the agent's
email address, choose a permission level (Member or Admin), and click **Send Invites**. The agent account will receive
the invitation and can accept it via `POST /current/user/invitations/acceptall/`.

**How the human invites the agent to a workspace:**

Open the workspace, click the member avatars in the toolbar, then click **Manage Members**. Enter the agent's email
address, choose a permission level, and optionally check **Invite to org** to add them to the organization at the same
time. Click **Send Invites** — if the agent isn't already an org member and the toggle is off, they'll need an org
invite separately.

Alternatively, the human can invite the agent programmatically:
- **Org:** `POST /current/org/{org_id}/members/{agent_email}/` with `permission` level
- **Workspace:** `POST /current/workspace/{workspace_id}/members/{agent_email}/` with `permission` level

### Option 4: PKCE Browser Login — Secure Authentication Without Sharing Passwords

For the most secure authentication flow — especially when a human wants to authorize an agent without sharing their
password — use the PKCE (Proof Key for Code Exchange) browser login. No credentials pass through the agent at any point.

The `client_id` can be a pre-registered ID, a dynamically registered ID (via DCR), or an **HTTPS URL pointing to a
Client ID Metadata Document (CIMD)**. CIMD is the MCP specification's preferred registration method — the server
fetches client metadata from the URL on-the-fly, so no pre-registration is needed.

1. Agent calls `POST /current/oauth/authorize/` with PKCE parameters (`code_challenge`, `code_challenge_method=S256`,
   `client_id`, `redirect_uri`, `response_type=code`) — gets back an authorization URL
2. The user opens the URL in their browser, signs in (supports SSO), and approves access
3. The browser displays an authorization code that the user copies back to the agent
4. Agent calls `POST /current/oauth/token/` with `grant_type=authorization_code`, the authorization `code`, and the
   PKCE `code_verifier` — receives an access token and refresh token
5. The agent is now authenticated. Access tokens last **1 hour**; refresh tokens are **long-lived**. Use
   `POST /current/oauth/token/` with `grant_type=refresh_token` to get new access tokens without repeating the flow.

This is the recommended approach when:
- A human wants to grant agent access without sharing their password
- The organization uses SSO and password-based auth isn't available
- You need the strongest security guarantees (no credentials stored by the agent)

### Recommendations

| Scenario | Recommended Approach |
|----------|---------------------|
| Operating autonomously, storing files, building for users | Create your own account and org (your personal collection of workspaces) and select a paid plan |
| Helping a human manage their existing account | Ask the human to create an API key for you |
| Working within a human's org with your own identity | Create an account, have the human invite you to their org or workspace |
| Building inside a human's already-paid organization | Be invited as a member of the human's org or workspace and build there |
| Human wants to authorize an agent without sharing credentials | Use PKCE browser login (Option 4) |
| Terminal workflows, scripting, or CI/CD pipelines | Install the CLI: `npm install -g @vividengine/fastio-cli` |
| Local AI agent needing Fastio access | Use the CLI's built-in MCP server: `fastio mcp` |

### Authentication & Token Lifecycle

All API requests require `Authorization: Bearer {token}` in the header. How you get that token depends on your access
pattern:

**JWT tokens (agent accounts):** Authenticate with `GET /current/user/auth/` using HTTP Basic Auth (email:password). The
response includes an `auth_token` (JWT). OAuth access tokens last **1 hour** and refresh tokens are **long-lived**. When
your token expires, re-authenticate to get a new one. If the account has 2FA enabled, the initial token has limited
scope until 2FA verification is completed via `/current/user/auth/2factor/auth/{token}/`.

**API keys (human accounts):** API keys are long-lived and do not expire unless the human revokes them. No refresh flow
needed.

**Verify your token:** Call `GET /current/user/auth/check/` at any time to validate your current token and get the
authenticated user's ID. This is useful at startup to confirm your credentials are valid before beginning work, or to
detect an expired token without waiting for a 401 error on a real request.

### OAuth Scopes — Controlling Access

When using PKCE browser login, you can request scoped access tokens that limit what the agent can do. Scopes follow
an inheritance model — broader scopes automatically include access to their children.

**Scope types:**

| Scope Type       | Description                                                                 |
|------------------|-----------------------------------------------------------------------------|
| `all_orgs`       | Access to all organizations the user owns or is a member of                 |
| `all_workspaces` | Access to all workspaces across accessible organizations                    |
| `all_shares`     | Access to all shares across accessible organizations and workspaces         |

**Inheritance:** `all_orgs` includes `all_workspaces`, which includes `all_shares`. Requesting `all_orgs` grants full
access to all orgs, workspaces, and shares the user has access to.

Pass the desired `scope_type` when initiating the PKCE authorization flow (`POST /current/oauth/authorize/`). If
omitted, the token defaults to full access (equivalent to `all_orgs`).

**Scope format note:** The `scope` parameter in the authorization request accepts the named strings listed above
(e.g., `scope=org`). However, API responses return scopes in a different format -- as arrays of
`entity_type:entity_id:access_mode` strings (e.g., `["org:12345:rw", "org:67890:r"]`). The token endpoint
(`POST /current/oauth/token/`) returns `scopes` as a JSON-encoded string that must be parsed. Use
`GET /current/auth/scopes/` to introspect the current token's scopes in a structured format.

### Organizations — Collectors of Workspaces

An organization (org) is a collector of workspaces. It can represent a company, a business unit, a team, or simply your own personal collection. Every workspace and share lives under an org, and orgs are the billable entity — storage, credits, and member limits are tracked at the org level.

### Internal vs External Orgs

When working with Fastio, an agent may interact with orgs in two different ways:

**Internal orgs** — orgs you created or were invited to join as a member. You have org-level access: you can see all
workspaces (subject to permissions), manage settings if you're an admin, and appear in the org's member list. Your own
orgs always show `member: true` in API responses.

**External orgs** — orgs you can access only through workspace membership. If a human invites you to their workspace
but does not invite you to their org, the org appears as external. You can see the org's name and basic public info, but
you cannot manage org settings, see other workspaces, or add members at the org level. External orgs show
`member: false` in API responses.

This distinction matters because an agent invited to a single workspace cannot assume it has access to the rest of that
org. It can only work within the workspaces it was explicitly invited to.

**Full org discovery requires both endpoints:**

- `GET /current/orgs/list/` — returns orgs you are a member of (`member: true`)
- `GET /current/orgs/list/external/` — returns orgs you access via workspace membership only (`member: false`)

**Always call both.** An agent that only calls `/orgs/list/` will miss every org where it was invited to a workspace but
not to the org itself — which is the most common pattern when a human adds an agent to help with a specific project. If
you skip `/orgs/list/external/`, you won't discover those workspaces at all.

**Example:** A human invites your agent to their "Q4 Reports" workspace. You can upload files, run AI queries, and
collaborate in that workspace. But you cannot create new workspaces in their org, view their billing, or access their
other workspaces. The org shows up in `/orgs/list/external/` — not `/orgs/list/`.

If the human later invites you to the org itself (via org member invitation), the org moves from external to internal and
you gain org-level access based on your permission level.

### Pagination

All list endpoints support offset-based pagination via query parameters. Use pagination to keep responses within token
limits and iterate through large collections.

**Query parameters:**

| Parameter | Type | Default | Max | Description                |
|-----------|------|---------|-----|----------------------------|
| `limit`   | int  | 100     | 500 | Number of items to return  |
| `offset`  | int  | 0       | —   | Number of items to skip    |

**Response metadata:** Every paginated response includes a `pagination` object:

```json
{
  "pagination": {
    "total": 42,
    "limit": 100,
    "offset": 0,
    "has_more": false
  }
}
```

**Paginating through results:**

```
# First page
GET /current/orgs/list/?limit=10&offset=0
# → pagination.has_more = true, pagination.total = 42

# Second page
GET /current/orgs/list/?limit=10&offset=10
# → pagination.has_more = true

# Continue until has_more = false
```

**Endpoints supporting pagination:**

| Endpoint                                             | Collection Key     |
|------------------------------------------------------|--------------------|
| `GET /current/orgs/all/`                             | `orgs`             |
| `GET /current/orgs/list/`                            | `orgs`             |
| `GET /current/orgs/list/external/`                   | `orgs`             |
| `GET /current/shares/all/`                           | `shares`           |
| `GET /current/workspace/{id}/members/list/`          | `users`            |
| `GET /current/org/{id}/billing/usage/members/list/`  | `billable_members` |
| `GET /current/workspace/{id}/list/shares/`           | `shares`           |
| `GET /current/user/me/list/shares/`                  | `shares`           |
| `GET /current/org/{id}/list/workspaces/`             | `workspaces`       |
| `GET /current/org/{id}/members/list/`                | `users`            |
| `GET /current/workspace/{id}/storage/search/`        | `files`            |
| `GET /current/share/{id}/storage/search/`            | `files`            |
| `GET /current/share/{id}/members/list/`              | `users`            |

**Pending members in member lists:** Member list responses include a `status` field for each user: `"active"` for
members who have accepted their invitation, or `"pending"` for users who have been invited but have not yet created an
account. Pending members also include an `invite` object with `id`, `created`, and `expires` fields. To cancel a
pending invitation, use `DELETE` on the invitation endpoint with the `invite.id`.

### Compact Responses (`output=`)

Most list and detail endpoints accept an optional `output` query parameter with three detail levels for nodes
(files/folders/notes/links), events, users, workspaces, orgs, and shares. Picking the right level is the single
biggest lever you have for keeping agent payloads small without losing information you actually need.

- **`terse`** — identifiers, primary labels, and the handful of fields needed to navigate between resources.
  Use for tree traversal, pickers, autocomplete, mention suggestions, and any prefetch step that will follow up
  with a detail call only on user (or agent) interest.
- **`standard`** — `terse` plus the operational context most list/detail views render: timestamps, lifecycle
  flags, short descriptions, plan/status fields, creator/owner refs, member status, short summaries. **This is
  the recommended default for most agent list and detail workflows** — it covers the fields a typical agent
  needs to reason about a resource without pulling branding, capability matrices, or long-form AI summaries.
- **`full`** — the complete resource shape; equivalent to omitting `?output=` entirely. Use when you need
  branding, capability matrices, permission blocks, long-form AI summaries, metadata blocks, EXIF, virus state,
  or any field specifically called out under the `full` tier in the category references.

**Rules:**
- **Syntax:** `?output=<level>` or `?output=<level>,<modifier>` (comma-separated tokens).
- **Mutually exclusive levels:** Specifying more than one detail level in the same request (e.g.
  `?output=terse,standard`) is an error and returns **HTTP 406**. Pick exactly one.
- **Cumulative fields:** `standard` is a superset of `terse`, and `full` is a superset of `standard` — nothing
  disappears as you move up a tier.
- **Default:** When `output=` is absent, responses are `full` and byte-for-byte unchanged.
- **Unknown tokens:** Silently ignored for forward compatibility.
- **`markdown` modifier:** Add `markdown` to any request (e.g. `?output=terse,markdown` or `?output=markdown` alone) to receive the response as GitHub-flavored Markdown instead of JSON. Response `Content-Type` becomes `text/markdown; charset=UTF-8`. Homogeneous record lists render as GFM pipe tables, associative maps as bullet lists, and error envelopes as a leading `# Error` section — including a nested `params` table that lists every parameter that failed validation when the call was a 406. Validation errors (HTTP 406) render as markdown too when the modifier is present. If the caller is an LLM that reasons better over markdown than JSON, prefer `?output=standard,markdown`.

Example markdown response body for `GET /current/user/details/?output=terse,markdown`:

```markdown
**Result:** success

# user
- **id:** 1234567890123456789
- **account_type:** agent
- **first_name:** Alice
- **last_name:** Example
- **profile_pic:** https://…
```

Example markdown response body for a validation error (HTTP 406) — note the nested `params` table:

```markdown
**Result:** failure

# Error
- **code:** 10022
- **text:** email: This value should not be blank. domain: This value should not be blank.
- **resource:** POST /current/user/email/

## params

| name | kind | message | code | expected_type |
|------|------|---------|------|---------------|
| email | missing | This value should not be blank. | 10022 | — |
| domain | invalid | This value is not a valid hostname. | 10023 | string |
```

Category-specific detail pages (linked from the LLM reference) list exactly which fields appear at each level
for each resource type.

### Retired per-field error codes

As of 2026-05-05, error codes `136957`, `249170`, `279705`, `295625` are retired. The equivalent field-level failures now surface inside `error.params[]` with `kind: 'invalid'` (and a per-field `code` and `message` describing the specific violation). Clients that previously switched on those exact integers should switch on `params[].name` + `params[].kind` instead.

---

## Core Capabilities

### 1. Workspaces — Shared Spaces for Agentic Teams

Workspaces are where agentic teams do their work. Each workspace has its own storage, member list, AI chat, and
activity feed — a shared environment where agents collaborate with other agents and with humans.

- **Included storage scales with your plan** — query org billing/usage for your org's storage allowance
- **File size limits are plan-dependent** (up to 40 GB on paid plans) — query `/upload/limits/` for exact values
- **File versioning** — every edit creates a new version, old versions are recoverable
- **Folder hierarchy** — organize files however you want
- **Full-text and semantic search** — find files by name or content, and documents by meaning
- **Member roles** — Owner, Admin, Editor, Viewer with granular permissions
- **Real-time sync** — changes appear instantly for all members via WebSockets

#### Intelligence: On or Off

Workspaces have an **intelligence** toggle that controls whether AI features are active. This is a critical decision:

**Intelligence OFF** — the workspace stores files without AI indexing. You can still attach files directly to an AI chat
conversation (up to 20 files), but files are not persistently indexed. This is fine for coordination workflows where
you don't need to query your content.

**Intelligence ON** — the workspace becomes an AI-powered knowledge base. Every document and code file uploaded is automatically ingested,
summarized, and indexed for RAG. This enables:

- **RAG (retrieval-augmented generation)** — scope AI chat to entire folders or the full workspace and ask questions
  across your indexed documents and code. The AI retrieves relevant passages and answers with citations.
- **Semantic search** — find files by meaning, not just keywords. "Show me contracts with indemnity clauses" works even
  if those exact words don't appear in the filename.
- **Auto-summarization** — short and long summaries generated for every indexed document and code file, searchable and visible in the UI.
- **Metadata extraction** — AI pulls structured metadata from documents, code, and images automatically using templates.
  Assign a template to a workspace, and every document uploaded is automatically extracted against that schema during
  ingestion. You can also trigger extraction manually or in batch. See section 14 (Metadata) for the full API.

> **Coming soon:** RAG indexing support for images, video, and audio files. Currently only documents and code are indexed.

> **Plan requirement.** Enabling `intelligence=true` requires both the `content_ai` and `ai_agent` plan features
> (included on every paid plan: Starter, Business, or Growth). On a plan that does not include those features, `intelligence` defaults to `false` on new
> workspaces and cannot be set to `true` — the API rejects the request with `1605 (Invalid Input)`. On paid plans that
> include `ai_agent`, agent accounts default `intelligence` to `true` when the parameter is omitted at create time. See
> the [AI reference](https://api.fast.io/current/llms/ai/#plan-requirements) for the full matrix.

On plans that support intelligence, **agents should explicitly set `intelligence=false` unless the user needs RAG
queries across many documents or AI-powered semantic search.** The ingestion cost (10 credits/page) is significant and
non-refundable — a 100-page document costs 1,000 credits to ingest. If your team only needs a shared workspace for
coordination, disable it to conserve credits.

**Agent use case:** Create a workspace per project or client. Enable intelligence only if agents or humans need to query the
content via RAG or semantic search (and the plan supports it). Upload reports, datasets, and deliverables. Invite other
agents and human stakeholders. Everything is organized, searchable, and versioned — and the whole team can see it.

> **Cost-saving tips:** Disable intelligence on storage-only workspaces to avoid ingestion costs. Attach-only AI chat
> (up to 20 files without indexing) requires a plan with `ai_agent` (included on every paid plan), so make sure the org is
> on a plan that includes it before relying on file Q&A.

### 2. Shares — Structured Agent-Human Exchange

Shares are purpose-built spaces for exchanging files between your agentic team and external humans. Three modes cover
every exchange pattern:

| Mode         | What It Does                  | Agent Use Case                                |
|--------------|-------------------------------|-----------------------------------------------|
| **Send**     | Recipients can download files | Deliver reports, exports, generated content   |
| **Receive**  | Recipients can upload files   | Collect documents, datasets, user submissions |
| **Exchange** | Both upload and download      | Collaborative workflows, review cycles        |

#### Share Features

- **Password protection** — require a password for link access
- **Expiration dates** — shares auto-expire after a set period
- **Download security** — three levels: `off` (no restrictions, default), `medium` (file previews are available but direct downloads are restricted for guests), or `high` (downloads completely disabled for guests). Set via `download_security` when creating or updating a share
- **Access levels** — `'Only members of the Share or Workspace'`, `'Members of the Share, Workspace or Org'`, `'Anyone with a registered account'`, or `'Anyone with the link'`
- **Custom branding** — background images, gradient colors, accent colors, logos
- **Post-download messaging** — show custom messages and links after download
- **Up to 3 custom links** per share for context or calls-to-action
- **Guest chat** — let share recipients ask questions in real-time
- **AI-powered auto-titling** — shares automatically generate smart titles from their contents
- **Activity notifications** — get notified when files are sent or received
- **Comment controls** — configure who can see and post comments (owners, guests, or both)

#### Two Storage Modes

When creating a share, you choose a `storage_mode` that determines how the share's files are managed:

- **`room`** (independent storage, default) — the share has its own isolated storage. Files are added directly to the
  share and are independent of any workspace. This creates a self-contained portal — changes to workspace files don't
  affect the portal, and vice versa. Perfect for final deliverables, compliance packages, archived reports, or any
  scenario where you want an immutable snapshot.

- **`workspace_folder`** (workspace-backed) — the share is backed by a specific folder in a workspace. The share displays
  the live contents of that folder — any files added, updated, or removed in the workspace folder are immediately
  reflected in the share. No file duplication, so no extra storage cost. To create a shared folder, pass
  `storage_mode=workspace_folder` and `folder_node_id={folder_opaque_id}` when creating the share. Note: expiration dates
  are not allowed on shared folder shares since the content is live. **Intelligence is not available** on shared folder
  shares — files are indexed through the parent workspace instead.

Both modes look the same to share recipients — a branded portal with file preview, download controls, and all share
features. The difference is whether the content is a snapshot (portal) or a live view (shared folder).

> **Note:** API responses include both `storage_mode` and a response-only `share_category` field.
> `independent` maps to `share_category: "portal"`; `workspace_folder` maps to `share_category: "shared_folder"`.

**Agent use case:** Generate a quarterly report, create a Send share with your client's branding, set a 30-day
expiration, and share the link. The client sees a branded page with instant file preview — not a raw download link.

### 3. File Share — Durable Single-File Share

Need to share one file with a stable link that doesn't expire on you? A **File Share** is a durable, link-shareable
view of a single workspace file. Create it once with `POST /current/workspace/{workspace_id}/create/fileshare/`,
pointing at the file's node id, and you get a permanent link — no expiration, no per-link transfer cap.

Choose how open the link is with `access_option`:

- `anyone_with_link` — anyone, no account needed
- `any_registered` — any signed-in Fastio user
- `named_people` — only people you explicitly grant access (the default)

Layer per-user grants on top — `view`, `download`, or `edit` — and optionally protect the link with a password
(sent on the public read endpoints via the `x-ve-password` request header, never the URL).

**Write-back (external edit).** A grantee with an `edit` capability can replace the shared file's content without
being a member of your workspace — they point a normal `action=update` upload session at the **File Share id** as the
`instance_id`. Supply an optional `if_version_id` precondition for safe compare-and-swap: if someone else replaced the
file first, the upload terminates with `status: assembly_failed` and `status_message: CONFLICT_VERSION_MISMATCH:{current_version_id}`,
so the client can re-read and retry. This makes a File Share a tiny, durable collaboration surface — not just a handoff
link.

**Owner-side visibility (one-directional).** Comments that recipients leave on a File Share are visible to the
owning workspace's members: they appear (read-only) on the shared file's comment thread in the workspace, show up in
workspace unified search, and surface as comment activity on the workspace's realtime/activity feed. The reverse is
never true — a File Share recipient only ever sees the comments made under that File Share, never the workspace's
internal comments. Replies, edits, and deletes of a File Share comment go through the File Share's own endpoints.

**Agent use case:** Publish a generated report at a stable URL a human keeps bookmarked, then grant a reviewer `edit`
so they can push a corrected version straight back — your agent sees a `file_share_content_updated` event when they do.

> **QuickShare is deprecated.** The old zero-config QuickShare create path now returns 403 (`10756 (Quickshare Deprecated)`); use File Share instead.
> Existing QuickShare links keep serving (view / download / revoke) during the drain.

### 4. Built-In AI — Ask Questions About Your Files

Fastio's AI is a **full agent** — beyond reading and analyzing your file contents, it can take actions on your
behalf, such as creating documents and notes and organizing your content. It operates within your permissions and
plan entitlements, and (like the direct MCP/API tools) its actions consume credits. You can delegate work to it
directly, or use the MCP/API tools yourself.

Fastio's AI lets agents query documents through two chat types, with or without persistent indexing. Both types
augment file knowledge with information from the web when relevant.

#### How the Agent Uses Files

There is no chat "type" to choose — a single agent surface handles every conversation and adapts to what you send. Ask
a general question and it answers from its own knowledge. When the workspace has **intelligence enabled**, it can search
the scope's indexed files and answer with citations (RAG). And you can focus a turn on specific files by attaching
**reference items**:

1. **File references** — attach specific files directly. The AI reads the full content of the attached files. Does not
   require intelligence — any AI-eligible file (with a ready preview or summary) can be attached. Max 20 files, 200 MB total.

2. **Folder references** — attach a folder so the AI grounds answers in its indexed files (RAG), answering with
   citations. Requires intelligence enabled and files in `ready` AI state.

Both are expressed the same way — as reference items in the `references`, `content_parts`, or `subjects` array of a
create-chat or send-message request; the backend resolves each item's full details server-side.

#### Intelligence Setting — When to Enable It

The `intelligence` toggle on a workspace controls whether uploaded documents and code files are automatically ingested, summarized, and
indexed for RAG. **For most workflows, intelligence should be OFF.** Ingestion costs 10 credits/page and is non-refundable — a 100-page
document costs 1,000 credits. This can be the largest credit consumer for agent accounts.

**Enable intelligence only when:**
- You have many files and need RAG queries across them to answer questions
- You want scoped RAG queries against folders or the entire workspace
- You need AI-powered semantic search across large document sets
- You're building a persistent knowledge base that will be queried repeatedly

**Disable intelligence (recommended default) when:**
- You're using the workspace for file storage, sharing, or team coordination
- You only need to analyze specific files (use file attachments instead — no intelligence needed)
- You're uploading deliverables, reports, or outputs that don't need to be queried
- You want to conserve credits — disabling avoids all ingestion costs

Even with intelligence disabled, you can still attach **file references** to a chat — any file that has a
ready preview can be attached directly for one-off analysis.

#### AI State — File Readiness for RAG

Every document and code file in an intelligent workspace has an `ai_state` field that tracks its ingestion progress:

| State         | Meaning                                           |
|---------------|---------------------------------------------------|
| `disabled`    | AI processing disabled for this file              |
| `pending`     | Queued for processing                             |
| `in_progress` | Currently being ingested and indexed              |
| `ready`       | Processing complete — file is available for RAG   |
| `failed`      | Processing failed                                 |

**Only documents and code files with `ai_state: ready` are included in folder/file scope searches.** If you upload files and immediately
create a scoped chat, recently uploaded files may not yet be indexed. Use the activity polling endpoint to wait for
`ai_state` changes before querying.

#### Attaching Files and Folders

| Feature              | Folder references (RAG)                     | File references (direct)                 |
|----------------------|--------------------------------------------|------------------------------------------|
| How it works         | Grounds answers in a folder's indexed files| Files read directly by AI                |
| Requires intelligence| Yes                                        | No                                       |
| Requires `ai_state`  | Files must be `ready`                      | File must have a ready preview/summary   |
| Best for             | Many files, knowledge retrieval            | Specific files, direct analysis          |
| Limits               | Up to 100 file/folder references total     | 20 files, 200 MB total                   |
| Default behavior     | Attach nothing = entire workspace          | N/A                                      |

Attach files and folders as **reference items** in the `references`, `content_parts`, or `subjects` array. Each item is a
bare `{type, id}` plus an optional version pin — the backend resolves the full details server-side after checking your
access and the file's AI-readiness.

**File reference:**
```json
{ "type": "file", "id": "{node_id}", "file_details": { "node_id": "{node_id}", "version_id": "{version_id}" } }
```
- `version_id` is optional — omit it to attach the file's current version. Get it from the file's `version` field in
  storage list/details responses.
- Only **file** (or note) nodes are accepted. The file must be AI-eligible (have a ready preview or summary).

**Folder reference:**
```json
{ "type": "folder", "id": "{node_id}", "folder_details": { "node_id": "{node_id}" } }
```
- Attaches a folder so the AI grounds answers in its indexed files (RAG). Only **folder** nodes are accepted.
- **Default scope is the entire workspace** — attach no folder references and the AI searches all indexed documents.

**Strict validation:** a referenced file or folder that cannot be attached — missing, inaccessible, deleted, the wrong
node type, or not AI-eligible — **fails the request** (`1609 (Not Found)` or `1605 (Invalid Input)`; folder attachment
that is not permitted in a share returns `1680 (Access Denied)`). References are no longer silently dropped.

#### Notes as Knowledge Grounding

Notes are markdown documents created directly in workspace storage via the API
(`POST /current/workspace/{id}/storage/{folder}/createnote/`). In an intelligent workspace, notes are ingested and
indexed just like uploaded files. This makes notes a way to store long-term knowledge that becomes grounding material
for future AI queries.

**Agent use case:** Store project context, decision logs, or reference material as notes. When you later ask the AI
"What was the rationale for choosing vendor X?", the note containing that decision is retrieved and cited — even months
later.

Notes within a folder scope are included in RAG queries when intelligence is enabled.

#### How to Write Effective Questions

The way you phrase questions depends on whether you're using folder scope (RAG) or file attachments.

**With folder/file scope (RAG):**

Write questions that are likely to match content in your indexed files. The AI searches the scope for relevant passages,
retrieves them, and uses them as citations to answer your question. Think of it as a search query that returns context
for an answer.

- Good: "What are the payment terms in the vendor contracts?" — matches specific content in files
- Good: "Summarize the key findings from the Q3 analysis reports" — retrieves relevant sections
- Good: "What risks were identified in the security audit?" — finds specific content to cite
- Bad: "Tell me about these files" — too vague for retrieval, no specific content to match
- Bad: "What's in this workspace?" — the AI can't meaningfully search for "everything"

If no folder scope is specified, the search defaults to all indexed documents in the workspace. For large workspaces, narrowing the
scope to specific folders improves relevance and reduces token usage.

**With file attachments:**

You can be more direct and simplistic since the AI reads the full file content. No retrieval step — the AI has the
complete file in context.

- "Describe this image in detail"
- "Extract all dates and amounts from this invoice"
- "Convert this CSV data into a summary table"
- "What programming language is this code written in and what does it do?"

**Controlling response length and style:** There is no `personality` parameter. Control verbosity and tone directly in
the question itself — for example, "In one sentence, summarize this report" or "List only the file names, no
explanations." Agents that need to extract data or get quick answers should ask for terse output explicitly; ask for a
thorough treatment when you need analysis with supporting evidence.

#### Waiting for AI Responses

After sending a message, the AI processes it asynchronously. You need to wait for the response to be ready.

**Turn statuses:**

| Status        | Meaning                              |
|---------------|--------------------------------------|
| `pending`     | Queued for processing (non-terminal) |
| `running`     | AI is generating the response (non-terminal) |
| `complete`    | Response finished                    |
| `failed`      | Processing failed                    |
| `cancelled`   | The turn was cancelled               |
| `lost`        | The turn was lost (worker failure)   |
| `needs_input` | The assistant needs more information and returned a single clarifying question instead of a full response (terminal, not an error). Read the question from the message details (`result.clarification`), then send the user's answer as a new message in the same chat. |

**Option 1: SSE streaming (recommended for real-time display)**

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

Returns a `text/event-stream` with response chunks as they're generated. The stream ends with a `done` event when the
response is complete (or a `needs_input` event when the assistant returned a clarifying question — listen for it as its
own event and send the user's answer as a new message). Response chunks include the AI's text, citations pointing to
specific files/pages/snippets, and
any structured data (tables, analysis).

**Option 2: Activity polling (recommended for background processing)**

Don't poll the message endpoint in a loop. Instead, use the activity long-poll:

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

When `ai_chat:{chatId}` appears in the activity response, the chat has been updated — fetch the message details to get
the completed response. This is the most efficient approach when you don't need to stream the response in real-time.

**Option 3: Fetch completed response**

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

Check the `status` field. When it is `complete`, the `result` blob contains the answer and its `citations` (each
pointing to specific files, pages, and snippets). The returned `message` object also carries a `message.actions` list (`turn.actions` on the share
endpoint) — the ordered, replayable record of the actions the AI took during the message (each entry has `seq`, `label`,
`state` of `running`/`done`/`failed`/`cancelled`, `affected_refs`, and `started_at` / `ended_at` timestamps) — so you
can render what it did, not just what it said.

#### Linking Users to AI Chats

To send a user directly to an AI chat in the workspace UI, append a `chat` query parameter to the workspace storage
URL:

`https://{org.domain}.fast.io/workspace/{workspace.folder_name}/storage/root?chat={chat_opaque_id}`

This opens the workspace with the specified chat visible in the AI panel.

#### Supported Content Types

**Indexed for RAG** (requires Intelligence ON):
- Documents (PDF, Word, text, markdown)
- Code files (all common languages)

**File attachments only** (no RAG indexing):
- Spreadsheets (Excel, CSV)
- Images (all common formats) — *RAG indexing coming soon*
- Video (all common formats) — *RAG indexing coming soon*
- Audio (all common formats) — *RAG indexing coming soon*

#### AI Share — Export to External AI Tools

Generate temporary download URLs for your files, formatted as markdown, for pasting into external AI assistants like
ChatGPT or Claude. Up to 25 files, 50MB per file, 100MB total. Links expire after 5 minutes. This is separate from the
built-in AI chat — use it when you want to analyze files with a different model or tool.

**Agent use case:** A user asks "What were Q3 margins?" You have 50 financial documents in an intelligent workspace.
Instead of downloading and parsing all 50, create a chat scoped to the finance folder and ask. The AI
searches the indexed content, retrieves relevant passages, and answers with citations. Pass the cited answer — with
source references — back to the user.

#### Semantic Search — Fast Retrieval Without LLM

Semantic search lets you find relevant document chunks by meaning without creating a chat or waiting for an LLM response.
It returns ranked text snippets with relevance scores — no LLM round-trip, no token cost beyond the search itself.

**When to use search vs chat:**

| | Semantic Search | AI Chat |
|---|---|---|
| **What it does** | Returns raw document chunks ranked by relevance | Creates an LLM-generated answer with citations |
| **Speed** | Fast — vector lookup only | Slower — retrieval + LLM generation |
| **Cost** | Low — no LLM token cost | Higher — LLM tokens consumed per message |
| **Best for** | Retrieval, lookup, finding specific content | Synthesis, analysis, summarizing across documents |
| **Returns** | Text snippets + scores + file references | Natural language answer + citations |

**Endpoints:**

Semantic search is now available via the unified storage search endpoint. The `/ai/search/` endpoints are deprecated.

```
GET /current/workspace/{workspace_id}/storage/search/?search={query}
GET /current/share/{share_id}/storage/search/?search={query}
```

When workspace intelligence is enabled, results automatically include semantic matches with `relevance_score`, `content_snippet`, `match_source`, `mimetype`, `media_segment`, and `search_metadata` fields.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `search` | 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. |

Requires `intelligence=true` on the workspace or share. Only files with `ai_state: ready` are included.

**Response example:**

```json
{
  "result": true,
  "results": [
    {
      "content": "The quarterly revenue showed a 15% increase...",
      "score": 0.95,
      "node": {
        "id": "f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf",
        "type": "file",
        "name": "quarterly-report.pdf",
        "mimetype": "application/pdf",
        "ai": { "state": "ready", "attach": true, "summary": true }
      }
    }
  ],
  "pagination": { "total": 25, "limit": 100, "offset": 0, "has_more": false }
}
```

Each result contains:
- `content` — the matched text snippet from the indexed document
- `score` — relevance score (0.0-1.0, higher is more relevant)
- `node` — full file resource (or `null` if the file was deleted)

With intelligence enabled, the `/storage/search` response also includes:
- `content_snippet` — the actual matching text from semantic search. NULL for keyword-only matches.
- `mimetype` — file MIME type (e.g., `application/pdf`, `audio/mpeg`). Present for semantic matches.
- `media_segment` — `{start_seconds, end_seconds}` identifying the timestamp range in audio/video where the match was found. Only present for audio/video file matches, enabling deep-linking to the exact moment.
- `relevance_score` — semantic relevance score (0.0-1.0)
- `match_source` — source of the match: `keyword`, `semantic`, or `both`

**With `details=true`** — the unified `/storage/search` endpoint enriches each file entry with a `node` field containing the full node resource:

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

**Agent memory pattern:** Upload context documents (meeting notes, research, reference material) to an intelligent
workspace. Later, use `storage/search` to retrieve relevant chunks without burning LLM credits. This is significantly
cheaper than creating a chat for every lookup and is ideal for agents that need to recall information across sessions —
treat the intelligent workspace as a persistent memory store and search as the retrieval mechanism.

**Agent use case — multi-step research:** An agent researching a topic uploads 200 papers to a workspace with
intelligence enabled. For each research question, it calls `storage/search` to find the most relevant passages, reads the
top results, and only escalates to AI chat when it needs the LLM to synthesize across multiple sources. This approach
uses a fraction of the credits compared to chatting for every question.

#### Unified Search — One Query, Grouped by Type

When you want to search across more than just files in a single call, use the unified search endpoint. It returns results
**grouped by type** into buckets — `files`, `metadata`, and `comments` (the share variant returns the
applicable subset) — each with its own pagination and its own health status.

```
GET /current/workspace/{workspace_id}/search/?search={query}
GET /current/share/{share_id}/search/?search={query}
```

This is a **REST-only** capability — there is no MCP tool for unified search; agents call the endpoint directly. Pass
per-bucket offset/limit params (`files_offset`/`files_limit`, `comments_offset`/`comments_limit`, and likewise for
`metadata`) to page each bucket independently. Every applicable bucket is always searched (the share
endpoint omits `metadata`). Each result item carries a `relevance_score` and an `updated` timestamp plus type-specific
fields. Every bucket is permission-filtered against the same rules as that type's dedicated endpoint, and counts are
computed after permission filtering — search never reveals an item, snippet, or count the caller cannot see. The
workspace `comments` bucket also includes comments left via the workspace's own File Shares (owner-side visibility);
File Share recipients never gain search access to workspace comments. A transient
problem affecting one bucket returns that bucket as `status: degraded` (empty results) without affecting the others. See
*Unified Search* in the Storage Operations reference for the full request/response contract. Use the per-type endpoints
(`storage/search`, `metadata/search`) when you only need one kind of result; use unified search when you want a single
grouped sweep.

### Built-In Help — Ask How-To Questions About Fastio

Beyond querying *your own* files, an agent can ask Fastio **how to use Fastio itself** and get a grounded, product-aware
answer in a single call. `POST /current/how-to/` takes a natural-language `question` (required, ≤2000 chars)
plus optional free-text `context` (≤8000 chars, treated as background data only) and returns HTTP 200 in one of two shapes:

- `status: "answer"` — a grounded `answer` string, plus `escalated` (retained for backward compatibility; always `false` — no deeper pass exists) and `topics_used`.
- `status: "needs_clarification"` — a `questions` array to put back to the user when the question is too vague to answer well.

Branch on `status`; treat `needs_clarification` as a normal prompt for more detail, not an error. The endpoint is
a **top-level, user-authenticated endpoint** — no org in the URL. Access is **open**: there is **no org-membership
requirement, no AI-Agent plan-feature gate, no active-subscription requirement, and no billable entity** — any
authenticated, available user may ask. How-to is **free**: no org, user, or any entity is charged; the LLM call runs
with skip-billing. Abuse is bounded solely by a per-user rate limit (429 + `x-ve-limit-*` headers) plus a per-user
fail-fast mutex — only one how-to request per user runs at a time, so a concurrent second call returns
`145858 (Rate Limited)` / HTTP 429 immediately. Use this when an integration needs authoritative usage guidance at runtime
instead of scraping the docs.
See the [How-To reference](https://api.fast.io/current/llms/howto/) for the full contract.

**Agent use case:** Mid-task, your agent is unsure how to put a password on a share link. It calls
`POST /current/how-to/` with the question, gets back step-by-step API guidance grounded in Fastio's own docs,
and proceeds — no human round-trip, no doc scraping.

### 5. File Preview — No Download Required

Files uploaded to Fastio get automatic preview generation. When humans open a share or workspace, they see the content
immediately — no "download and open in another app" friction.

**Supported preview formats:**

- **Images** — full-resolution with auto-rotation and zoom
- **Video** — HLS adaptive streaming (50-60% faster load than raw video)
- **Audio** — interactive waveform visualization
- **PDF** — page navigation, zoom, text selection
- **Spreadsheets** — grid navigation with multi-sheet support
- **Code & text** — syntax highlighting, markdown rendering

**Agent use case:** Your generated PDF report doesn't just appear as a download link. The human sees it rendered inline,
can flip through pages, zoom in, and comment on specific sections — all without leaving the browser.

### 6. Notes — Markdown Documents as Knowledge

Notes are a storage node type (alongside files and folders) that store markdown content directly on the server. They
live in the same folder hierarchy as files, are versioned like any other node, and appear in storage listings with
`type: "note"`.

#### Creating and Updating Notes

**Create:** `POST /current/workspace/{id}/storage/{parent_id}/createnote/`

- `name` (required) — filename, must end in `.md`, max 100 characters (e.g., `"project-context.md"`)
- `content` (required) — markdown text, max 100 KB. Must be valid UTF-8 (UTF8MB4). Control characters (`\p{C}` except `\t`, `\n`, `\r`) are stripped.

**Update:** `POST /current/workspace/{id}/storage/{node_id}/updatenote/`

- `name` (optional) — rename the note (must end in `.md`)
- `content` (optional) — replace the markdown content (max 100 KB). Must be valid UTF-8 (UTF8MB4). Control characters (`\p{C}` except `\t`, `\n`, `\r`) are stripped.
- At least one of `name` or `content` must be provided

Notes can also be moved, copied, deleted, and restored using the same storage endpoints as files and folders.

#### Notes as Long-Term Knowledge Grounding

In an intelligent workspace, notes are automatically ingested and indexed just like uploaded documents. This makes notes a
powerful way to **bank knowledge over time** — any facts, context, or decisions stored in notes become grounding
material for future AI queries.

When an AI chat uses folder scope (or defaults to the entire workspace), notes within that scope are searched alongside
files. The AI retrieves relevant passages from notes and cites them in its answers.

**Use cases:**
- Store project context, decisions, and rationale as notes. Months later, ask "Why did we choose vendor X?" and the AI
  retrieves the note with that decision.
- After researching a topic, save key findings in a note. Future AI chats automatically use those findings as grounding.
- Create reference documents (style guides, naming conventions, process docs) that inform all future AI queries in the
  workspace.

#### Linking Users to Notes

**Open a note in the workspace UI** — append `?note={opaque_id}` to the workspace storage URL:

`https://{org.domain}.fast.io/workspace/{folder_name}/storage/root?note={note_opaque_id}`

**Link directly to the note preview** — use the standard file preview URL:

`https://{org.domain}.fast.io/workspace/{folder_name}/preview/{note_opaque_id}`

The preview link is more effective if you want the user to focus on reading just that note, while the `?note=` link
opens the note within the full workspace context.

### 7. Comments & Annotations

Humans can leave feedback directly on files, anchored to specific content:

- **Image comments** — anchored to regions of the image
- **Video comments** — anchored to timestamps with frame-stepping and spatial region selection
- **Audio comments** — anchored to timestamps or time ranges
- **PDF comments** — anchored to specific pages with optional text selection
- **Threaded replies** — single-level threads under each comment (replies to replies are auto-flattened)
- **Emoji reactions** — one reaction per user per comment, new replaces previous
- **Mentions** — tag users with `@[user:USER_ID:Display Name]` syntax in the comment body
- **Attachments** — attach up to 25 objects (a file or folder, a sign envelope, a share, a File Share, or a workspace) to a comment as references, inline at create time or via the comment attachment endpoints. Attachment display names are access-gated on read, so render defensively — when an attachment reports `available: false`, never show a name

**Character limits:** The comment `body` field has a hard maximum of **8,192 characters** — this includes the full mention tag markup. When you convert a short `@name` reference into the bracket syntax (e.g., `@[user:abc123:Jane Smith]`), the expanded string is what counts toward the limit. The display text (everything except mention markup) is separately capped at **500 characters**. Build your comment body with the expanded tags first, then verify the total length before submitting.

**Linking users to comments:** Link users to the file preview URL. The comments sidebar opens automatically in workspace
previews, and in share previews when comments are enabled on the share.

Base preview URL:

`https://{org.domain}.fast.io/workspace/{folder_name}/preview/{file_opaque_id}`

For shares: `https://go.fast.io/shared/{custom_name}/{title-slug}/preview/{file_opaque_id}`

**Deep linking to a specific comment:** Append `?comment={comment_id}` to the preview URL. The UI scrolls to and
highlights the comment automatically:

`https://{org.domain}.fast.io/workspace/{folder_name}/preview/{file_opaque_id}?comment={comment_id}`

**Deep linking to media/document positions:** For comments anchored to specific locations, combine with position
parameters:

- `?t={seconds}` — seeks to a timestamp in audio/video (e.g., `?comment={id}&t=45.5`)
- `?p={pageNum}` — navigates to a page in PDFs (e.g., `?comment={id}&p=3`)

**Agent use case:** You generate a design mockup. The human comments "Change the header color" on a specific region of
the image. You read the comment, see exactly what region they're referring to, and regenerate.

### 8. File Uploads — Getting Files Into Fastio

Agents upload files through a session-based API. There are two paths depending on file size:

#### Small Files (Under 4 MB)

For files under 4 MB, upload in a single request. Send the file as `multipart/form-data` with the `chunk` field
containing the file data, plus `org` (your org domain), `name`, `size`, and `action=create`.

To have the file automatically added to a workspace or share, include `instance_id` (the workspace or share ID) and
optionally `folder_id` (the target folder's OpaqueId, or omit for root). The response includes `new_file_id` — the
permanent OpaqueId of the file in storage. No further steps needed.

```
POST /current/upload/
Content-Type: multipart/form-data

Fields: org, name, size, action=create, instance_id, folder_id, chunk (file)
→ Response: { "result": true, "id": "session-id", "new_file_id": "2abc..." }
```

#### Large Files (4 MB and Above)

Large files use chunked uploads. The flow has five steps:

1. **Create a session** — `POST /current/upload/` with `org`, `name`, `size`, `action=create`, `instance_id`, and
   optionally `folder_id`. Returns a session `id`.

2. **Upload chunks** — Split the file into chunks (chunk size is plan-dependent — query `/upload/limits/` for the exact
   value; last chunk may be smaller). For each chunk, send
   `POST /current/upload/{session_id}/chunk/` as `multipart/form-data` with the `chunk` field (binary data), `order`
   (1-based — first chunk is `order=1`), and `size`. You can upload up to **3 chunks in parallel** per session.

3. **Trigger assembly** — Once all chunks are uploaded, call `POST /current/upload/{session_id}/complete/`. The server
   verifies chunks and combines them into a single file.

4. **Poll for completion** — The upload progresses through states asynchronously. Poll the session details with the
   built-in long-poll:

   `GET /current/upload/{session_id}/details/?wait=60`

   The server holds the connection for up to 60 seconds and returns immediately when the status changes:

   | Status | Meaning | What to Do |
   |--------|---------|------------|
   | `ready` | Awaiting chunks | Upload chunks |
   | `uploading` | Receiving chunks | Continue uploading |
   | `assembling` | Combining chunks | Keep polling |
   | `complete` | Assembled, awaiting storage import. Not accessible for download/preview — can only be imported to storage locations. Can be imported to multiple locations. | Keep polling |
   | `storing` | Being added to storage | Keep polling |
   | **`stored`** | **Done** — file is in storage | Read `new_file_id`, clean up |
   | `assembly_failed` | Assembly error (terminal) | Check `status_message` |
   | `store_failed` | Storage import failed (terminal) | Check `status_message`, handle error |

   Stop polling when status is `stored`, `assembly_failed`, or `store_failed`.

5. **Clean up** — Delete the session after completion: `DELETE /current/upload/{session_id}/`.

#### Optional Integrity Hashing

Include `hash` (SHA-256 hex digest) and `hash_algo=sha256` on each chunk for server-side integrity verification. You can
also provide a full-file hash in the session creation request instead.

#### Resuming Interrupted Uploads

If a connection drops mid-upload, the session persists on the server. To resume:

1. Fetch the session: `GET /current/upload/{session_id}/details/`
2. Read the `chunks` map — keys are chunk numbers already uploaded, values are byte sizes
3. Upload only the missing chunks
4. Trigger assembly and continue as normal

#### Manual Storage Placement

If you omit `instance_id` when creating the session, the file is uploaded but not placed in any workspace or share. You
can add it to storage manually afterward:

```
POST /current/workspace/{id}/storage/{folder}/addfile/
Body: from={"type":"upload","upload":{"id":"{session_id}"}}
```

This is useful when you need to upload first and decide where to place the file later.

#### MCP Binary Upload — Three Approaches

MCP agents have three ways to pass binary data when uploading chunks. Each uses the `upload` tool's `chunk` action
with exactly one of `data`, `blob_ref`, or `content` (for text):

**1. `data` parameter (base64) — simplest for MCP agents**

Pass base64-encoded binary directly in the `data` parameter of the `chunk` action. No extra steps required. Works
with any MCP client. Adds ~33% size overhead from base64 encoding.

**2. `stage-blob` action — MCP tool-based blob staging**

Use the `upload` tool's `stage-blob` action with `data` (base64) to pre-stage binary data as a blob. Returns a
`blob_id` that you pass as `blob_ref` in the `chunk` call. Useful when decoupling staging from uploading or preparing
multiple chunks in advance.

1. `upload` action `stage-blob` with `data` (base64-encoded binary) → returns `{ blob_id, size }`
2. `upload` action `chunk` with `blob_ref` set to the `blob_id`

**3. `POST /blob` endpoint — HTTP blob staging for non-MCP clients**

A sidecar HTTP endpoint that accepts raw binary data outside the JSON-RPC pipe, avoiding base64 encoding entirely.
Useful for clients that can make direct HTTP requests alongside MCP tool calls.

1. `POST /blob` with `Mcp-Session-Id` header and raw bytes as the request body → returns `{ blob_id, size }`
2. `upload` action `chunk` with `blob_ref` set to the `blob_id`

**Blob constraints (apply to both staging methods):**
- Blobs expire after **5 minutes** — stage and consume them promptly
- Each blob is consumed (deleted) on first use and cannot be reused
- Maximum blob size: **100 MB**

**Agent use case:** You're generating a 200 MB report. Create an upload session targeting the client's workspace, split
the file into chunks (size from `/upload/limits/`), upload 3 at a time, trigger assembly, and poll until `stored`. The file appears in the
workspace with previews generated automatically. Use the activity polling endpoint (section 13) to know when AI indexing
completes if intelligence is enabled.

### 9. URL Import — Pull Files From Anywhere

When you need to add a file from the web, use `POST /current/web_upload/` with `source_url` instead of downloading it
locally and re-uploading. This is faster because the file transfers server-to-server — your agent never touches the
bytes.

- Supports any HTTP/HTTPS URL
- Supports OAuth-protected sources: **Google Drive, OneDrive, Dropbox**
- Files go through the same processing pipeline (preview generation, AI indexing if intelligence is enabled, virus
  scanning)

**Check progress after submitting.** Web uploads are processed asynchronously by Fastio's server-side fetch agent,
which may be blocked or rate-limited by the source. The import can fail silently if the source rejects the request, times
out, or returns an error. Monitor the upload status to confirm the file was actually retrieved and stored before
reporting success to the user.

**Security note:** The `web_upload` endpoint instructs the Fastio cloud server to fetch the URL — not the agent's
local environment. The Fastio server is a public cloud service with no access to the agent's local network, internal
systems, or private infrastructure. It can only reach publicly accessible URLs and supported OAuth-authenticated cloud
storage providers. No internal or private data is exposed beyond what the agent could already access through its own
network requests.

**Agent use case:** A user says "Add this Google Doc to the project." You call `POST /current/web_upload/` with the URL.
Fastio downloads it server-side, generates previews, indexes it for AI, and it appears in the workspace. No local I/O —
and no bandwidth consumed by your agent.

### 10. Real-Time Collaboration

Fastio uses WebSockets for instant updates across all connected clients:

- **Live presence** — see who's currently viewing a workspace or share
- **Cursor tracking** — see where other users are navigating
- **Follow mode** — click a user to mirror their exact navigation
- **Instant file sync** — uploads, edits, and deletions appear immediately for all viewers

### 11. Events — Real-Time Audit Trail

Events give agents a real-time audit trail of everything that happens across an organization. Instead of scanning entire
workspaces to detect what changed, query the events feed to see exactly which files were uploaded, modified, renamed, or
deleted — and by whom, and when. This makes it practical to build workflows that react to changes: processing a document
the moment it arrives, flagging unexpected permission changes, or generating a daily summary of activity for a human.

The activity log is also the most efficient way for an agent to stay in sync with a workspace over time. Rather than
periodically listing every file and comparing against a previous snapshot, check events since your last poll to get a
precise diff. This is especially valuable in large workspaces where full directory listings are expensive.

#### What Events Cover

- **File operations** — uploads, downloads, moves, renames, deletes, version changes
- **Membership changes** — new members added, roles changed, members removed
- **Share activity** — share created, accessed, files downloaded by recipients
- **Settings updates** — workspace or org configuration changes
- **Billing events** — credit usage, plan changes
- **AI operations** — ingestion started, indexing complete, chat activity

#### Querying Events

Search and filter events with `GET /current/events/search/`:

- **Scope by profile** — filter by `workspace_id`, `share_id`, `org_id`, or `user_id`
- **Filter by type** — narrow to specific event names, categories, or subcategories (see reference below)
- **Date range** — use `created-min` and `created-max` for time-bounded queries
- **Pagination** — offset-based with `limit` (1-250) and `offset`

Get full details for a single event with `GET /current/event/{event_id}/details/`, or mark it as read with
`GET /current/event/{event_id}/ack/`.

#### Event Categories

Use the `category` parameter to filter by broad area:

| Category      | What It Covers                                        |
|---------------|-------------------------------------------------------|
| `user`        | Account creation, updates, deletion, avatar changes   |
| `org`         | Organization lifecycle, settings                       |
| `workspace`   | Workspace creation, updates, archival, file operations |
| `share`       | Share lifecycle, settings, file operations              |
| `node`        | File and folder operations (cross-profile)             |
| `ai`          | AI chat, summaries, RAG indexing                       |
| `invitation`  | Member invitations sent, accepted, declined            |
| `billing`     | Subscriptions, trials, credit usage                    |
| `domain`      | Custom domain configuration                            |
| `apps`        | Application integrations                               |
| `metadata`    | Metadata extraction, templates, key-value updates      |

#### Event Subcategories

Use the `subcategory` parameter for finer filtering within a category:

| Subcategory      | What It Covers                                       |
|------------------|------------------------------------------------------|
| `storage`        | File/folder add, move, copy, delete, restore, download |
| `comments`       | Comment created, updated, deleted, mentioned, replied, reaction |
| `members`        | Member added/removed from org, workspace, or share   |
| `lifecycle`      | Profile created, updated, deleted, archived          |
| `settings`       | Configuration and preference changes                 |
| `security`       | Security-related events (2FA, password)              |
| `authentication` | Login, SSO, session events                           |
| `ai`             | AI processing, chat, indexing                        |
| `invitations`    | Invitation management                                |
| `billing`        | Subscription and payment events                      |
| `assets`         | Avatar/asset updates                                 |
| `upload`         | Upload session management                            |
| `transfer`       | Cross-profile file transfers                         |
| `import_export`  | Data import/export operations                        |
| `quickshare`     | Quick share operations                               |
| `metadata`       | Metadata operations                                  |

#### Common Event Names

Use the `event` parameter to filter by exact event name. Here are the most useful ones for agents:

**File operations (workspace):**
`workspace_storage_file_added`, `workspace_storage_file_deleted`, `workspace_storage_file_moved`,
`workspace_storage_file_copied`, `workspace_storage_file_updated`, `workspace_storage_file_restored`,
`workspace_storage_folder_created`, `workspace_storage_folder_deleted`, `workspace_storage_folder_moved`,
`workspace_storage_download_token_created`, `workspace_storage_zip_downloaded`,
`workspace_storage_file_version_restored`, `workspace_storage_link_added`

**File operations (share):**
`share_storage_file_added`, `share_storage_file_deleted`, `share_storage_file_moved`,
`share_storage_file_copied`, `share_storage_file_updated`, `share_storage_file_restored`,
`share_storage_folder_created`, `share_storage_folder_deleted`, `share_storage_folder_moved`,
`share_storage_download_token_created`, `share_storage_zip_downloaded`

**Comments:**
`comment_created`, `comment_updated`, `comment_deleted`, `comment_mentioned`, `comment_replied`,
`comment_reaction`

**Membership:**
`added_member_to_org`, `added_member_to_workspace`, `added_member_to_share`,
`removed_member_from_org`, `removed_member_from_workspace`, `removed_member_from_share`,
`membership_updated`

**Workspace lifecycle:**
`workspace_created`, `workspace_updated`, `workspace_deleted`, `workspace_archived`, `workspace_unarchived`

**Share lifecycle:**
`share_created`, `share_updated`, `share_deleted`, `share_archived`, `share_unarchived`,
`share_imported_to_workspace`

**File Share lifecycle:**
`file_share_created`, `file_share_updated`, `file_share_content_updated`, `file_share_deleted`,
`file_share_access_granted`, `file_share_access_revoked` (`file_share_content_updated` fires when an
`edit`-grant holder replaces the shared file's content)

**AI:**
`ai_chat_created`, `ai_chat_new_message`, `ai_chat_updated`, `ai_chat_deleted`, `ai_chat_published` (chat publish is currently disabled platform-wide -- `capabilities.can_publish_agent_chat` is `false`),
`node_ai_summary_created`, `workspace_ai_share_created`

**Metadata:**
`metadata_kv_update`, `metadata_kv_delete`, `metadata_kv_extract`,
`metadata_template_update`, `metadata_template_delete`,
`metadata_view_update`, `metadata_view_delete`, `metadata_template_select` (deprecated — the originating endpoint now returns access-denied)

**Quick shares (deprecated — see File Share lifecycle):**
`workspace_quickshare_created`, `workspace_quickshare_updated`, `workspace_quickshare_deleted`,
`workspace_quickshare_file_downloaded`, `workspace_quickshare_file_previewed`

**Invitations:**
`invitation_email_sent`, `invitation_accepted`, `invitation_declined`

**User:**
`user_created`, `user_updated`, `user_deleted`, `user_email_reset`, `user_asset_updated`

**Org:**
`org_created`, `org_updated`, `org_closed`

**Billing:**
`subscription_created`, `subscription_cancelled`, `billing_free_trial_ended`

#### Example Queries

**Recent comments in a workspace:**
```
GET /current/events/search/?workspace_id={id}&subcategory=comments&limit=50
```

**Files uploaded to a share in the last 24 hours:**
```
GET /current/events/search/?share_id={id}&event=share_storage_file_added&created-min=2025-01-19 00:00:00 UTC
```

**All membership changes across an org:**
```
GET /current/events/search/?org_id={id}&subcategory=members
```

**AI activity in a workspace:**
```
GET /current/events/search/?workspace_id={id}&category=ai
```

**Who downloaded files from a share:**
```
GET /current/events/search/?share_id={id}&event=share_storage_download_token_created
```

#### AI-Powered Summaries

Request a natural language recap of recent activity with `GET /current/events/search/summarize/`. Returns event counts,
category breakdowns, and a narrative summary. Focus the summary on a specific workspace or share, or summarize across
the entire org.

**Agent use case — stay in sync:** You manage a workspace with 10,000 files. Instead of listing the entire directory
tree to find what changed, query events since your last check. You get a precise list: "3 files uploaded, 1 renamed,
2 new members added" — with timestamps, actors, and affected resources.

**Agent use case — react to changes:** A client uploads tax documents to a Receive share. The events feed shows the
upload immediately. Your agent detects it, processes the documents, and notifies the accountant — no polling the file
list required.

**Agent use case — report to humans:** A human asks "What happened on the project this week?" You call the AI summary
endpoint scoped to their workspace and return a clean narrative report — no log parsing required.

### 12. Activity Polling — Wait for Changes Efficiently

After triggering an async operation (uploading a file, enabling intelligence, creating a share), don't loop on the
resource endpoint to check if it's done. Instead, use the activity long-poll endpoint:

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

The `{entity_id}` is the profile ID of the resource you're watching — a workspace ID, share ID, or org ID. For
**upload sessions**, use the **user ID** (since uploads are user-scoped, not workspace-scoped until the file is added
to storage).

The server holds the connection open for up to 95 seconds and returns **immediately** when something changes on that
entity — file uploads complete, previews finish generating, AI indexing completes, comments are added, etc.

The response includes activity keys that tell you *what* changed (e.g., `storage:{fileId}` for file changes,
`preview:{fileId}` for preview readiness, `ai_chat:{chatId}` for chat updates, `ai_state:{fileId}` for AI indexing
state changes, `upload:{uploadId}` for upload completion). Pass the returned `lastactivity` timestamp into your next
poll to receive only newer changes.

This gives you near-instant reactivity with a single open connection per entity, instead of hammering individual
endpoints.

**WebSocket upgrade:** For true real-time delivery (~300ms latency vs ~1s for polling), connect via WebSocket at
`wss://{host}/api/websocket/?{auth_token}` — the JWT is the entire query string (no `token=` key, no other query parameters). The server pushes activity arrays as they happen:

```json
{"response": "activity", "activity": ["storage:2abc...", "preview:2abc..."]}
```

You then fetch only the resources that changed. If the WebSocket connection fails, fall back to long-polling — the data
is identical, just slightly higher latency.

**Share channels are scoped per recipient.** On a share channel, each frame is scoped to the receiving guest's share
access before delivery: a guest is pushed only the changes their access level permits them to observe, and content
detail is collapsed or dropped for guests with restricted file visibility. Do not assume a share guest will be notified
of every change on the channel. Members and all non-share channels (user / organization / workspace) receive
the full frame. Share tokens are also shorter-lived — inspect `expires_in` on the auth response and refresh before it
elapses.

**Agent use case:** You upload a 500-page PDF and need to know when AI indexing is complete before querying it. Instead
of polling the file details endpoint every few seconds, open a single long-poll on the workspace. When
`ai_state:{fileId}` appears in the activity response, the file is indexed and ready for AI chat.

### 13. Metadata — Structured Data on Files

The metadata system lets agents attach structured, typed key-value data to files. This goes beyond filenames and
timestamps — you can store invoice amounts, contract parties, document categories, or any domain-specific fields, then
query and sort files by those fields.

#### Architecture

The system has three layers:

1. **Templates** — define a metadata schema: named fields with types (`string`, `int`, `float`, `bool`, `json`, `url`,
   `datetime`), constraints (`min`, `max`, `fixed_list`), and descriptions. Templates belong to a workspace (a template
   name is 1–100 characters, a description up to 255). The metadata feature is available on all plan tiers, with three caps that
   scale by plan — templates per workspace, files per template, and fields (columns) per template: Starter=2/1000/10,
   Business=10/1000/50, Growth=10/1000/50. Listing, details, and preview-match endpoints return
   `plan_node_limit`, `is_truncated`, and the unfiltered count alongside the visible count so frontends can render
   upsell messaging; the `/details` and `/list` endpoints also return `field_count` and `plan_field_limit` for the
   field cap. Each field carries an `autoextract` boolean (default true); templates must declare at least one
   autoextract-eligible field and auto-extraction jobs filter their default scope to those fields. Node responses
   (metadata details + template nodes listing) carry an `autoextractable` boolean — true when the node is a
   non-trashed file with a completed AI summary — so clients can gate "extract now" affordances. 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.

2. **Template-Node Mappings** — many-to-many relationships between templates and files. Files are linked to templates
   either manually (add/remove endpoints) or automatically via AI-based matching. A template can be applied to multiple
   files, and a file can have metadata from multiple templates.

3. **Node Metadata** — the actual key-value pairs stored on individual files. Each file's metadata is split into
   **template metadata** (conforming to mapped template field definitions) and **custom metadata** (user-defined
   fields not tied to any template).

#### Template Management

| Endpoint | Description |
|----------|-------------|
| `POST /current/workspace/{id}/metadata/templates/` | Create a template (name, description, fields JSON — there is no `category` parameter) |
| `DELETE /current/workspace/{id}/metadata/templates/` | Delete a template |
| `GET /current/workspace/{id}/metadata/templates/list/` | List templates (sub-paths: `all`, `custom`, `system`, `enabled`, `disabled`) |
| `GET /current/workspace/{id}/metadata/templates/{template_id}/details/` | Get template details with all fields |
| `POST /current/workspace/{id}/metadata/templates/{template_id}/update/` | Update definition (append `/create/` to copy). Response includes a `schema_update` object with `added_fields` and `type_changed_fields` when either triggers auto re-extraction across mapped files |

#### Template-Node Mapping

| Endpoint | Description |
|----------|-------------|
| `GET /current/workspace/{id}/metadata/eligible/` | List nodes (files and notes) eligible for metadata extraction (have summary + preview) |
| `POST /current/workspace/{id}/metadata/templates/{template_id}/nodes/add/` | Manually add nodes (files or notes) to a template |
| `POST /current/workspace/{id}/metadata/templates/{template_id}/nodes/remove/` | Remove nodes from a template |
| `GET /current/workspace/{id}/metadata/templates/{template_id}/nodes/` | List nodes mapped to a template |
| `POST /current/workspace/{id}/metadata/templates/{template_id}/auto-match/` | AI-based node matching to a template |
| `POST /current/workspace/{id}/metadata/templates/{template_id}/extract-all/` | Batch-extract metadata for all mapped nodes (async, returns job_id) |

#### Node Metadata Operations

| Endpoint | Description |
|----------|-------------|
| `GET /current/workspace/{id}/storage/{node_id}/metadata/details/` | Get all metadata (`template_metadata` + `custom_metadata`) |
| `POST /current/workspace/{id}/storage/{node_id}/metadata/update/{template_id}/` | Set/update key-value pairs |
| `DELETE /current/workspace/{id}/storage/{node_id}/metadata/` | Delete metadata keys |
| `POST /current/workspace/{id}/storage/{node_id}/metadata/extract/` | Enqueue async AI extraction for a single file (returns HTTP 202 + `job_id`; poll `/jobs/status/` and read values from `/metadata/details/` once `status: "completed"`) |
| `GET /current/workspace/{id}/storage/{node_id}/metadata/list/{template_id}/` | List files with metadata for a template |
| `GET /current/workspace/{id}/storage/{node_id}/metadata/templates/` | List templates in use across files |
| `GET /current/workspace/{id}/storage/{node_id}/metadata/versions/` | Metadata version history |

#### Saved Views

A saved view is a **per-user, per-template** display configuration (columns, sort, filters) for browsing a
template's metadata across files in a spreadsheet-like interface. It is keyed by (workspace, user, template) — it
is **not** node-scoped, and the view `id` is server-owned (never client-supplied).

| Endpoint | Description |
|----------|-------------|
| `GET /current/workspace/{id}/metadata/view/?template_id={template_id}` | Get the caller's saved view for a template (error `1609` if none exists) |
| `POST /current/workspace/{id}/metadata/view/` | Create or update the caller's saved view (form-encoded) |
| `DELETE /current/workspace/{id}/metadata/view/?template_id={template_id}` | Delete the caller's saved view for a template (error `1609` if none) |
| `GET /current/workspace/{id}/metadata/views/` | List the caller's saved views in the workspace (newest first) |
| `POST /current/workspace/{id}/metadata/view/{template_id}/export/` | Export a template's metadata to a TSV file in storage (async; returns `job_id`) |

The `POST` upsert body MUST be **form-encoded** (`application/x-www-form-urlencoded` or `multipart/form-data`) — a
JSON body returns HTTP 406. Fields: `template_id` (required), `config` (required; a JSON **string** of
`{version, columns, sort, filters}` where `filters` are AND-chained, max 5), and optional `name` (≤30 chars, a human
label preserved on upsert if omitted). Responses (`created`/`updated` timestamps) use the canonical
`Y-m-d H:i:s UTC` format.

The `export` endpoint takes `template_id` as a path segment (before `/export/`) and requires a saved view to already
exist for that template (error `1609` otherwise). Optional form fields: `parent_node_id` (destination folder; omit
for workspace root) and `filename`. An in-flight export for the same `(workspace, user, template, parent_node_id)`
returns `{status: "duplicate", filename, message}`; a queued export returns `{job_id, status: "queued", filename,
message}`. Poll the destination folder for the result.

#### AI-Powered Extraction

Metadata extraction works in three modes:

1. **Automatic (during ingestion)** — when intelligence is enabled and files are mapped to a template, every
   file uploaded is automatically extracted against the template schema during the ingestion pipeline. No API call
   needed — metadata appears on the file after ingestion completes. This includes documents, spreadsheets, images
   (PNG, JPEG, WebP up to 30 MB), and code files.

2. **Manual (per file)** — the extract endpoint (`POST .../metadata/extract/`) enqueues an async AI extraction job
   against the specified `template_id` (optional `fields` parameter restricts the scope to a subset of the template
   schema). The endpoint returns HTTP 202 Accepted with `{ job_id, template_id, node_id, fields, status, status_uri }`
   in milliseconds; the actual extraction runs in the background. Clients poll `GET /current/workspace/{id}/jobs/status/`
   and watch the `metadata_extract` array for a matching `kind: "single"` entry transitioning through
   `queued` → `in_progress` → `completed`, then fetch the values via
   `GET /current/workspace/{id}/storage/{node_id}/metadata/details/`. Submitting the same `(node, template, fields)`
   combination while a job is already in flight is idempotent — the existing `job_id` is returned and no duplicate is
   enqueued. Real-time activity events fire on every state transition for clients that prefer the activity stream
   over polling.

3. **Batch (per template)** — the template-level extract-all endpoint
   (`POST .../metadata/templates/{template_id}/extract-all/`) enqueues an async job that processes every file mapped to
   the template. Returns a `job_id` for tracking. This endpoint is rate-limited; see the global Rate Limiting section. Use this after adding
   files to a template to backfill metadata.

A daily background process also detects stale metadata — files whose extraction predates the template's last update —
and automatically re-extracts them, ensuring metadata stays current when templates evolve.

Additionally, the template-update endpoint itself auto-enqueues partial re-extraction at the moment of change for
meaningful schema edits: new field names (`added_fields`) and type changes on existing names (`type_changed_fields`).
Soft edits (description, min/max, nullable, fixed_list, regex) bump the schema hash but do not auto-trigger extraction
— invoke `extract-all` manually if you want those applied.

For example, uploading an invoice to a workspace and mapping it to a "financial" template automatically fills in fields
like `invoice_number`, `amount`, `vendor_name`, and `due_date` — no extraction call required if intelligence is enabled.

#### Field Definition Structure

When creating templates, each field in the `fields` JSON array supports:

| Property | Type | Description |
|----------|------|-------------|
| `name` | string | Field identifier (alphanumeric + underscore) |
| `description` | string | Human-readable description |
| `type` | string | `string`, `int`, `float`, `bool`, `json`, `url`, `datetime` |
| `min` | number | Minimum value constraint |
| `max` | number | Maximum value constraint |
| `default` | mixed | Default value |
| `fixed_list` | array | Allowed values (dropdown) |
| `can_be_null` | bool | Whether null is allowed |

#### Agent Use Cases

- **Automatic classification:** Create a template, use auto-match to map eligible files, enable intelligence. Every
  mapped file gets structured metadata extracted automatically — no manual extraction calls needed.
- **Data pipeline:** Create a workspace with an invoice template. Upload invoices and add them to the template (or use
  auto-match). Metadata (amounts, vendors, dates) is extracted automatically. Query by field values using the list
  endpoint.
- **Compliance tracking:** Create a template with required fields (review_date, reviewer, status). Map files to the
  template. The metadata view shows which files are missing required fields at a glance.
- **Bulk backfill:** Create a template, add files to it, then use template-level `extract-all` to batch-extract
  metadata for all mapped files. The daily staleness walker re-extracts when templates are updated.
- **Custom + template fields:** Files support both template metadata (structured, schema-enforced) and custom metadata
  (user-defined, ad-hoc). Use template fields for consistent extraction and custom fields for one-off annotations.

### 14. Reference Values — Enums & Constraints

This section lists valid values for commonly used parameters across the API.

#### Preview Types

Valid `preview_type` values (used in preview read/preauthorize endpoints):
`thumbnail`, `image`, `hlsstream`, `pdf`, `spreadsheet`

Preview states returned in file details: `unknown`, `not possible`, `not generated`, `error`, `in progress`, `ready`

#### Image Transformations

The `transform_name` in transform endpoints is `image`. Supported query parameters:

| Parameter | Values | Description |
|-----------|--------|-------------|
| `output-format` | `png`, `jpg` | Output image format |
| `width`, `height` | pixels | Resize dimensions |
| `cropwidth`, `cropheight` | pixels | Crop region size |
| `cropx`, `cropy` | pixels | Crop region origin |
| `rotate` | `0`, `90`, `180`, `270` | Rotation angle |
| `size` | `IconSmall`, `IconMedium`, `Preview` | Predefined size presets |

Transformation states: `rendered`, `rendering`, `unrendered`, `unable to render`

#### AI File States

When intelligence is enabled, each file progresses through AI processing states (visible in node details `ai.state`):
`disabled` → `pending` → `inprogress` → `ready` (or `failed`)

#### AI Chat Parameters

| Parameter | Constraint |
|-----------|-----------|
| `privacy` / `visibility` | `private`, `public` (default: `private`; **`public` is currently disabled platform-wide** -- creating a public chat returns `403`; check `capabilities.can_publish_agent_chat` (currently `false`) first) |
| `name` | Max 100 characters |
| `question` | 1–32,000 characters |
| `references` / `content_parts` / `subjects` / `uploads` | File/folder reference items — max 20 files, 200 MB, 100 references total |

#### Quick Share Constraints

- Single file only, max file size is plan-dependent (query `/upload/limits/` for exact value)
- Default expiration: 3 hours, maximum: 24 hours
- Auto-deleted on expiration, public access (no auth)
- Can update expiration but not beyond the original 24-hour window

#### Download Tokens

Use `GET .../storage/{node_id}/requestread/` to generate a temporary auth-free download token. Pass the returned
`token` as a query parameter: `GET .../storage/{node_id}/read/?token={token}`. This is useful for opening files in
browser tabs without sending Authorization headers.

**MCP agents** have additional download options: use the `download://` resource templates for direct content retrieval
(up to 50 MB), or the `/file/` HTTP pass-through endpoint for streaming larger files. See the "MCP Tool Architecture"
section for details.

#### Unit Calculations

- **Storage** is measured in GibiBytes (1024^3 bytes)
- **Bandwidth** is measured in GigaBytes (1000^3 bytes)

---

## Plans & Credits

New organizations — created by humans or agents alike — choose a **paid plan (Starter, Business, or Growth)** to get
started. A newly created organization must select a paid plan before it can do work; until then it is in an
upgrade-only state and resource-consuming endpoints return HTTP 402. There is no free-to-start path for new orgs.

### What Credits Cover

All platform activity consumes credits from the org's monthly allowance:

| Resource                | Cost                    |
|-------------------------|-------------------------|
| Storage                 | 100 credits/GB          |
| Bandwidth               | 212 credits/GB          |
| AI chat tokens          | 1 credit per 100 tokens |
| Document pages ingested | 10 credits/page         |
| Video ingested          | 5 credits/second        |
| Audio ingested          | 0.5 credits/second      |
| Images ingested         | 5 credits/image         |
| File conversions        | 25 credits/conversion   |

When credits run out, the org enters a reduced-capability state — file storage and access continue to work, but
credit-consuming operations (AI chat, file ingestion, bandwidth-heavy downloads) are limited until the credits reset or
the plan is upgraded. The org is never deleted.

**Detecting an upgrade-only or credit-exhausted org:** API calls return HTTP 402 with one of these error codes:

| Error Code | Description | Meaning |
|------------|-------------|---------|
| 1688 | Subscription Required | Org has no active paid plan (a new org that hasn't selected one yet), or its credits are exhausted |
| 1696 | Credit Limit Exceeded | Credit limit exceeded (error message includes credits used and credit limit) |

You can also check proactively: the `subscriber` field in org details (`GET /current/org/{org_id}/details/`) returns
`false` when the org has no active paid plan or is out of credits. Admins can check detailed usage via
`GET /current/org/{org_id}/billing/usage/limits/credits/`.

**When you hit the limit:** Upgrade the org's plan, or select one if the org is still in the upgrade-only state. Direct
the owner to `https://go.fast.io/onboarding` or use the billing API. Higher-tier plans include a larger monthly credit
allowance and expanded limits.

### Plan Entitlement Matrix

Starter, Business, and Growth are the paid plans new organizations choose:

| Feature                  | Starter | Business  | Growth    |
|--------------------------|---------|-----------|-----------|
| Monthly included credits | 300,000 | 1,200,000 | 4,500,000 |
| Storage                  | 1 TB    | 10 TB     | 50 TB     |
| Included seats           | 1       | 20        | 50        |
| Max file size            | 25 GB   | 50 GB     | 100 GB    |
| Workspaces               | 10      | 100       | Unlimited |
| Shares                   | 100     | 1,000     | 1,000     |

---

## Common Workflows

### Deliver a Report to a Client

1. Upload report PDF to workspace
2. Create a Send share with password protection and 30-day expiration
3. Share the link with the client
4. Client sees a branded page, previews the PDF inline, downloads if needed
5. You get a notification when they access it

### Collect Documents From a User

1. Create a Receive share ("Upload your tax documents here")
2. Share the link
3. User uploads files through a clean, branded interface
4. Files appear in your workspace, auto-indexed by AI (if intelligence is on)
5. Ask the AI: "Are all required forms present?"

### Build a Knowledge Base

1. Create a workspace **with intelligence enabled** (this is one of the workflows that justifies the ingestion cost)
2. Upload all reference documents
3. AI auto-indexes and summarizes everything on upload
4. Use **semantic search** (`storage/search`) for fast, low-cost retrieval — find relevant document chunks by meaning without an LLM round-trip. Best for lookup, recall, and memory workflows
5. Use **AI chat** when you need the LLM to synthesize, analyze, or summarize across documents — returns a natural language answer with citations
6. Combine both: search first to find relevant content cheaply, then chat only when synthesis is needed

### Set Up an Agentic Team Workspace

1. Create org + select a paid plan + workspace + folder structure
2. Upload templates and reference docs
3. Invite other agents and human team members to the org or workspace
4. Create shares for client deliverables (Send) and intake (Receive)
5. Configure branding, passwords, expiration
6. Humans and agents collaborate as members of the shared org — everyone sees the same fully configured platform

### Collaborative Review Cycle (Exchange Share)

1. Create an Exchange share ("Review these designs and upload your feedback")
2. Upload draft files for the recipient
3. Share the link — recipient can both download your files and upload theirs
4. Comments and annotations on files enable inline feedback
5. AI summarizes what changed between rounds (if intelligence is on)

### Extract Structured Metadata From Documents

1. Create a workspace **with intelligence enabled** (metadata extraction requires ingestion — budget for ingestion costs)
2. Create a metadata template with the fields you need (e.g., invoice_number, amount, vendor, due_date)
3. Upload files to the workspace
4. Add files to the template manually (`POST .../metadata/templates/{id}/nodes/add/`) or use AI auto-match (`POST .../metadata/templates/{id}/auto-match/`)
5. Mapped files have metadata automatically extracted during ingestion against the template schema
6. For existing files, use `POST .../metadata/templates/{id}/extract-all/` to batch-extract metadata for all mapped files
7. Query files by metadata fields using the list endpoint, or view in the spreadsheet-like metadata view
8. Custom fields can be added to any file independently of the template

### One-Off Document Analysis (No Intelligence Needed)

1. Create a workspace (intelligence off is fine)
2. Upload the files you want to analyze
3. Create an AI chat and attach the specific files directly (up to 20 files)
4. Ask questions — AI reads the attachments and responds with citations
5. No persistent indexing, no credit cost for ingestion

### Choose Between Portal and Shared Folder

**Use a Portal (independent storage) when:**

- Delivering final, immutable outputs (reports, compliance packages)
- You want a snapshot that won't change if workspace files are updated
- Files are "done" and shouldn't reflect future edits

**Use a Shared Folder (workspace-backed) when:**

- Files are actively being updated (live data feeds, ongoing projects)
- You want zero storage duplication
- Recipients should always see the latest version

### Manage Credit Budget

1. Check current usage: `GET /current/org/{org_id}/billing/usage/limits/credits/`
2. Storage costs 100 credits/GB — a 10 GB workspace costs 1,000 credits/month
3. Document ingestion costs 10 credits/page — a 50-page PDF costs 500 credits
4. Disable intelligence on storage-only workspaces to avoid ingestion costs
5. Use attach-only AI chat (no intelligence needed) for one-off analysis to save credits
6. When credits run low, upgrade the org's plan (Starter, Business, or Growth) for a larger monthly credit allowance

---

## CLI Tool

The `fastio` CLI provides full platform access from the terminal — authentication, file management, AI chat,
and more. It's built in Rust for cross-platform performance and supports macOS, Linux, and Windows.

### Installation

```bash
# NPM (recommended)
npm install -g @vividengine/fastio-cli

# Or run without installing
npx @vividengine/fastio-cli --help

# Shell script
curl -fsSL https://raw.githubusercontent.com/MediaFire/fastio_cli/main/install.sh | sh

# From source (Rust 1.85+)
cargo install --path .
```

Pre-compiled binaries are also available on the [GitHub releases page](https://github.com/MediaFire/fastio_cli/releases).

### Authentication

The CLI checks credentials in this priority order:

1. `--token` flag (one-off bearer token)
2. `FASTIO_TOKEN` environment variable
3. `FASTIO_API_KEY` environment variable
4. Stored profile credentials (default or named)

**Browser login (recommended):**

```bash
fastio auth login          # Opens browser for secure PKCE OAuth flow
fastio auth status         # Verify authentication
fastio auth logout         # Sign out
```

**Email/password:**

```bash
fastio auth login --email user@example.com --password ****
```

**API key:**

```bash
fastio auth api-key create --name "CI pipeline"
export FASTIO_API_KEY=your-key-here
```

**2FA:**

```bash
fastio auth 2fa status
fastio auth 2fa setup --channel totp
fastio auth 2fa verify <code>
```

### Quick Start

```bash
fastio auth login
fastio org list
fastio workspace create --org <org_id> "My Workspace"
fastio upload file --workspace <workspace_id> ./document.pdf
fastio download file --workspace <workspace_id> <node_id> --output ./downloads/
fastio ai chat --workspace <workspace_id> "What files do I have?"
```

### Command Reference

| Domain | Command | Operations |
|--------|---------|------------|
| **Auth & User** | `auth` | Login, logout, 2FA, API keys, OAuth sessions |
| | `user` | Profile, search, assets, invitations |
| | `configure` | CLI profiles and settings |
| **Orgs & Workspaces** | `org` | Create/read/update/delete, billing, members |
| | `workspace` | Create/read/update/delete, metadata templates, notes, quickshares |
| | `member` | Workspace/share member management |
| | `invitation` | Accept, decline, delete invitations |
| **Files & Storage** | `files` | List, create folders, move, copy, rename, delete, trash, versions, search, lock |
| | `upload` | Chunked uploads with progress, text uploads, URL imports |
| | `download` | Streaming downloads with progress, folder ZIP, batch, quickshare |
| | `lock` | Acquire, check, release file locks |
| **Shares** | `share` | Create/read/update/delete, file management, members, quickshares, password protection |
| | `comment` | Comments, replies, reactions, linking |
| | `event` | Activity events, search, polling |
| | `preview` | File preview URLs and transforms |
| | `asset` | Org/workspace/user asset management |
| **AI** | `ai` | Chat, search, history, message management, summarize |
| **Platform** | `apps` | App listing, details, launching |
| | `import` | Cloud import providers, identities, sources, jobs |
| | `mcp` | Built-in MCP server for AI agents |
| | `completions` | Shell completion generation |

### Output Formatting

```bash
fastio org list                              # Table (default)
fastio org list --format json                # JSON output
fastio org list --format csv                 # CSV output
fastio org list --fields name,id,description # Filter output fields
```

### Profile Management

The CLI supports multiple named profiles for switching between accounts:

```bash
fastio configure init                  # Initialize configuration
fastio auth login --profile work       # Authenticate a named profile
fastio org list --profile work         # Use a named profile
fastio configure set-default work      # Set default profile
fastio configure list                  # List all profiles
```

Configuration is stored in `~/.fastio/` (`config.json` for settings, `credentials.json` for tokens).

### Global Flags

| Flag | Purpose |
|------|---------|
| `--format json\|table\|csv` | Output format |
| `--fields name,id,...` | Filter output fields |
| `--no-color` | Disable colored output |
| `--quiet` / `-q` | Suppress output |
| `--verbose` / `-v` | Enable debug logging |
| `--profile <name>` | Use named profile |
| `--token <jwt>` | One-off bearer token |
| `--api-base <url>` | Override API base URL |

### Built-in MCP Server

The CLI can run as a local MCP server, enabling AI agents to use Fastio through the Model Context Protocol without
connecting to the hosted MCP server:

```bash
fastio mcp
```

**Claude Desktop configuration:**

```json
{
  "mcpServers": {
    "fastio": {
      "command": "fastio",
      "args": ["mcp"]
    }
  }
}
```

Filter available tools:

```bash
fastio mcp --tools auth,org,workspace,files,upload,download
```

### Shell Completions

```bash
fastio completions bash > ~/.bash_completion.d/fastio
fastio completions zsh > ~/.zfunc/_fastio
fastio completions fish > ~/.config/fish/completions/fastio.fish
fastio completions powershell > _fastio.ps1
```

### Source & License

- **Repository:** [github.com/MediaFire/fastio_cli](https://github.com/MediaFire/fastio_cli)
- **License:** Apache License 2.0

---

## MCP Tool Architecture

The MCP server exposes consolidated domain-specific tools, each covering a domain. Every tool uses
an `action` parameter to select the specific operation — agents don't need to discover hundreds of separate tools, just
a manageable set of tools with clearly named actions.

| Tool         | Domain                          | Example Actions                                                               |
|--------------|---------------------------------|-------------------------------------------------------------------------------|
| `auth`       | Authentication                  | `signin`, `signup`, `set-api-key`, `pkce-login`, `pkce-complete`, `status`, `signout` |
| `org`        | Organizations                   | `list`, `details`, `create`, `update`, `discover-all`                         |
| `workspace`  | Workspaces                      | `list`, `details`, `create`, `update`, `check-name`. (Its legacy `metadata-*` actions are **deprecated forwarding shims** to the `metadata` tool and will be removed next release — use `metadata` instead.) |
| `metadata`   | Metadata templates & saved views | `view-get`, `view-save`, `view-delete`, `views-list`, `view-export`, plus template-management and AI-extraction actions. Canonical surface for metadata templates and per-user saved views. (Per-file node metadata is on the `storage` tool, not here.) |
| `share`      | Shares                          | `list`, `create`, `update`, `delete`, `quickshare-create`                     |
| `storage`    | Files, folders, locks, previews, search (keyword + semantic when intelligence is enabled; accepts `files_scope`/`folders_scope` for scoped semantic search) | `list`, `details`, `search`, `create-folder`, `create-note`, `move`, `delete`, `lock-acquire`, `lock-status`, `lock-release`, `preview-url` (returns constructed `preview_url`), `preview-transform` (returns constructed `transform_url`) |
| `upload`     | File uploads                    | `create-session`, `stage-blob`, `chunk`, `finalize`, `text-file`, `web-import` |
| `download`   | Downloads                       | `file-url`, `zip-url`, `quickshare-details`                                   |
| `ai`         | AI chat (defaults to the entire workspace — attach nothing to search all indexed documents). Attach file/folder reference items to ground answers in specific files or folders. | `chat-create`, `message-send`, `message-read`, `chat-list` |
| `member`     | Members                         | `add`, `update`, `remove`, `details`                                          |
| `invitation` | Invitations                     | `list`, `send`, `revoke`, `accept-all`                                        |
| `asset`      | Branding assets                 | `types`, `list`, `upload`, `delete`                                           |
| `comment`    | Comments                        | `list`, `create`, `details`, `delete`                                         |
| `event`      | Events & audit                  | `search`, `details`, `summarize`, `activity-poll`                             |
| `user`       | Account mgmt                    | `me`, `update`, `invitation-list`, `allowed`                                  |
| `apps`       | Apps discovery                  | `list`                                                                        |
| `how-to` | Built-in product help — ask a natural-language "how do I…" question about Fastio and get a grounded answer (or a clarifying question) back. **Top-level, user-authenticated: no org required, no org membership or plan feature required — open to any authenticated caller, free (no entity is charged), bounded by a per-user rate limit.** `ask` takes a `question` (and optional `context`, `surface`). `surface` accepts `mcp` (MCP-tool phrasing) or `code` (code-mode execute-proxy phrasing, steps written as execute-proxy calls, e.g. `fastio.postJson('/current/<path>/', ...)`); omit for default REST-API phrasing. | `ask` |

> **Note on tool naming:** the tools above are listed without a vendor prefix (`auth`, `share`, `ai`, `how-to`, …),
> matching the names the MCP server advertises. The built-in help tool shipped as `how-to` (prefix-free, hyphenated —
> not `fastio_howto`). As a general practice, agents should discover the exact tool names from the MCP `tools/list`
> at connection time rather than hardcoding them.

### `web_url` in Tool Responses — Use It Instead of Building URLs

All entity-returning tool responses include a `web_url` field containing a ready-to-use link to the resource in the Fastio web UI.
**Use `web_url` directly** instead of constructing URLs manually from API response fields. This avoids errors from
slug generation, subdomain routing, or parameter formatting.

Tools that return `web_url`:

| Tool | Actions |
|------|---------|
| `org` | `list`, `details`, `create`, `update`, `public-details`, `list-workspaces`, `list-shares`, `create-workspace`, `discover-all`, `discover-available`, `discover-external` |
| `workspace` | `list`, `details`, `update`, `available`, `list-shares`, `create-note`, `update-note`, `quickshare-get`, `quickshares-list` |
| `share` | `list`, `details`, `create`, `update`, `public-details`, `available` |
| `storage` | `list`, `details`, `search`, `trash-list`, `create-folder`, `copy`, `move`, `rename`, `restore`, `add-file`, `version-list`, `version-restore`, `preview-url`, `preview-transform` |
| `ai` | `chat-create`, `chat-details`, `chat-list` |
| `upload` | `text-file`, `finalize` |
| `download` | `file-url`, `quickshare-details` |

When presenting links to users, always use `web_url` from tool responses. Never construct URLs manually.

**Resources** available via `resources/read`:
- `skill://guide` — full tool documentation with parameters and examples
- `session://status` — current authentication state
- `download://workspace/{workspace_id}/{node_id}` — download a workspace file (returns base64 content up to 50 MB)
- `download://share/{share_id}/{node_id}` — download a share file (returns base64 content up to 50 MB)
- `download://quickshare/{quickshare_id}` — download a quickshare file (public, no auth required, up to 50 MB)

The `download://` resource templates provide direct file content retrieval via the MCP `resources/read` protocol.
Files up to 50 MB are returned inline as base64 blobs. Larger files return a fallback message directing to the HTTP
pass-through endpoint (see below). The `download` tool's `file-url` and `quickshare-details` actions include a
`resource_uri` field in their response that points to the corresponding `download://` resource URI.

**HTTP pass-through endpoint** for file downloads:

The MCP server exposes a `/file/` HTTP endpoint that streams file content directly with proper `Content-Type`,
`Content-Length`, and `Content-Disposition` headers — useful for large files that exceed the 50 MB MCP resource limit
or when streaming is preferred over base64 encoding.

| Path | Auth | Description |
|------|------|-------------|
| `GET /file/workspace/{workspace_id}/{node_id}` | `Mcp-Session-Id` header required | Stream a workspace file |
| `GET /file/share/{share_id}/{node_id}` | `Mcp-Session-Id` header required | Stream a share file |
| `GET /file/quickshare/{quickshare_id}` | None (public) | Stream a quickshare file |

For workspace and share downloads, include the `Mcp-Session-Id` header from your active MCP session. The server uses
the session's auth token to fetch the file and streams it back.

**Query parameters:**
- `?error=html` — returns error pages as HTML instead of JSON (useful for browser-facing links)

**Size limits:** The `download://` resource templates return file content inline (base64) for files up to **50 MB**.
Larger files return a fallback message directing to the `/file/` HTTP pass-through endpoint.

**`web_url` in download responses:** The `download` tool's `file-url` and `quickshare-details` actions include both
a `resource_uri` (for MCP resource reads) and a `web_url` (for browser-facing links) in their responses.

**Resource completion** — The workspace and share `download://` resource templates support MCP `completion/complete`
for tab-completion of IDs. Agents can use this to discover valid workspace IDs, share IDs, and node IDs without
needing to call separate list actions first.

### Tool Annotations — Safety & Side Effects

All tools include explicit MCP annotations (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`,
`openWorldHint`) so agents and agent frameworks can make informed decisions about confirmation prompts, retries, and
automated execution.

**Read-only tools** (safe, no confirmation needed, `idempotentHint: true`):
- `download`, `event`, `apps` — these tools only read data, never modify state, and are safe to retry

**Non-destructive mutation tools** (create or update, no delete actions):
- `upload`, `invitation` — these tools create or modify resources but cannot delete them

**Destructive tools** (include delete, purge, or close actions — require user confirmation):
- `auth`, `user`, `org`, `workspace`, `share`, `storage`, `ai`, `comment`, `member`, `asset` — these tools have at
  least one action that permanently removes or closes a resource. Agent frameworks should prompt for confirmation before
  executing destructive actions.

**Discovery tools** (`openWorldHint: true`):
- `org`, `user`, `workspace`, `share`, `storage` — these tools can discover resources beyond the agent's current
  context. Agents may encounter resources they haven't seen before in list/search results.

**Credit-consuming operations** to be aware of:
- AI chat: 1 credit per 100 tokens
- File uploads: storage credits (100 credits/GB)
- Downloads: bandwidth credits (212 credits/GB)
- Document ingestion: 10 credits/page (when intelligence is enabled) — this can be the largest credit consumer. A 100-page document costs 1,000 credits to ingest.

### Code Mode — Streamlined Tools for Headless Agents

The MCP server (v2026.02.102+) detects the connecting client and serves one of two tool sets:

**Named Mode** (Claude Desktop, Cline, unknown clients): All core tools listed above plus app-specific widget tools — the full interactive experience with action-based routing across every
domain.

**Code Mode** (Claude Code, Cursor, Continue): A streamlined set of tools optimized for programmatic workflows:

| Tool       | Purpose                                                                                     |
|------------|---------------------------------------------------------------------------------------------|
| `auth`     | Authentication — same as Named Mode (`signin`, `signup`, `set-api-key`, `pkce-login`, etc.) |
| `upload`   | File uploads — same as Named Mode (`create-session`, `chunk`, `finalize`, `text-file`, etc.)|
| `search`   | Keyword/tag search across the public API endpoint catalog                                   |
| `execute`  | Make authenticated API calls to Fastio (structured method/path/body/params)                |

#### `search` Tool

Discovers API endpoints by keyword and tag. Returns scored matches with method, path, summary, parameters, and relevant
concept docs (pagination, error codes, etc.).

**Parameters:**

| Parameter          | Type    | Required | Description                                                    |
|--------------------|---------|----------|----------------------------------------------------------------|
| `query`            | string  | Yes      | Keyword search query (e.g., "list workspaces", "upload file")  |
| `tag`              | string  | No       | Filter results by API tag (e.g., "workspace", "storage", "ai") |
| `include_concepts` | boolean | No       | Include related concept docs (pagination, error codes, etc.)   |
| `max_results`      | number  | No       | Maximum number of endpoint matches to return                   |

#### `execute` Tool

Makes authenticated API calls to the Fastio API using structured parameters. The tool automatically injects the
session token, unwraps the API response envelope, and extracts errors — agents receive clean response data without
boilerplate. Non-JSON responses (text, binary) are handled gracefully.

**Parameters:**

| Parameter    | Type   | Required | Description                                                        |
|--------------|--------|----------|--------------------------------------------------------------------|
| `method`     | enum   | Yes      | HTTP method: `get`, `post`, `postJson`, `delete`, `put`            |
| `path`       | string | Yes      | API endpoint path (e.g., `/current/org/{id}/list/workspaces/`)     |
| `body`       | object | No       | Request body (for `post`, `postJson`, `put`)                       |
| `params`     | object | No       | Query string parameters                                            |
| `timeout_ms` | number | No       | Request timeout in milliseconds                                    |

**Special paths:**

| Path                           | Purpose                                           |
|--------------------------------|---------------------------------------------------|
| `/readnote/`                   | Read note content in code mode                    |
| `download://{file_id}`         | MCP resource path for reading file content        |

#### Code Mode Workflow Pattern

Code Mode agents follow a **search → review → execute → iterate** loop:

1. **Search** — use the `search` tool to discover relevant API endpoints by keyword or tag
2. **Review** — examine the returned endpoint details (method, path, parameters, summary)
3. **Execute** — call the endpoint with structured parameters (`method`, `path`, `body`, `params`)
4. **Iterate** — refine based on results, search for additional endpoints as needed

This pattern replaces the need for many individually named tools. Agents discover endpoints dynamically via search and
call them with structured parameters via execute, without needing pre-registered tool definitions for each operation.

**Example — list workspaces in an org:**

```
// Search: search tool with query "list workspaces"
// → returns: GET /current/org/{id}/list/workspaces/

// Execute:
execute method="get" path="/current/org/{org_id}/list/workspaces/"
```

### Response Hints — Guided Agent Workflows

Tool responses include structured hints that help agents navigate multi-step workflows, handle errors gracefully, and
understand resource state. Agents should read and act on these hints rather than guessing the next step.

**`_next` — Suggested next actions:**

Successful tool responses include a `_next` array of contextual next-step suggestions using exact tool names, action
names, and IDs from the response. Agents should follow these hints instead of guessing the next step or consulting
docs. Present on many actions across the tool set.

Example: after `storage` action `list`, `_next` might suggest `["storage folder-details {node_id}",
"download file-url {node_id}", "ai chat"]` with actual IDs from the response populated in the suggestions.

**`_warnings` — Destructive or gated action warnings:**

Actions that are destructive, irreversible, or have significant side effects include `_warnings` strings in their
response. Agents should read these warnings before proceeding and present them to the user when appropriate. Present on
the following actions:
- `storage`: purge, bulk copy/move/delete/restore (partial failure warnings)
- `workspace`: update (intelligence disable), archive, delete
- `org`: close, billing-create
- `share`: delete, archive, update (type change)
- `ai`: chat-delete
- `download`: file-url (token expiry), zip-url
- `upload`: stage-blob (5-minute expiry)

**`_recovery` — Error recovery hints:**

Error responses (`isError: true`) include `_recovery` hints as actionable bullet points appended to the error text.
Hints are matched by HTTP status code and error message patterns, guiding agents toward the
correct resolution. All errors also include `(during: <tool> <action>)` so agents know exactly which operation failed.

| Status | Recovery hint |
|--------|---------------|
| 400    | Bad request — check required parameters and value formats |
| 401    | Re-authenticate using `auth` action `signin` or `pkce-login` |
| 402    | Credits exhausted — check with `org` action `limits` |
| 403    | Permission denied — check role with `org` action `details` |
| 404    | Resource not found — verify the ID is correct |
| 405    | Method not allowed — check the action name is valid for this tool |
| 409    | Conflict — resource may already exist |
| 413    | Payload too large — reduce file size or use chunked upload |
| 422    | Validation failed — check field values against documented constraints |
| 429    | Rate limited — wait 2–4 seconds, retry with exponential backoff |

Error message pattern matching provides additional context-specific recovery steps (e.g., "email not verified" →
use `auth` action `email-verify`; "workspace not found" → check workspace ID with `workspace` action `list`).

**`ai_capabilities` — AI mode availability:**

Included in `workspace` action `details` responses. Shows the available AI modes for the workspace:
- **Intelligence ON:** file and folder references (full RAG with indexed search — attach folder references to ground answers in a folder's indexed files), plus the `search` action for semantic search (vector-based document chunk retrieval with relevance scores — no LLM round-trip, returns ranked snippets). Use search for fast retrieval/lookup; use chat for synthesis/analysis.
- **Intelligence OFF:** direct file references only (max 20 files, 200 MB total). Semantic search is not available.

**`_ai_state_legend` — File AI processing state:**

Included in `storage` action `list` and `search` responses when files have AI state. Describes the possible states:
- `ready` — file is indexed and available for AI queries
- `pending` — file is queued for AI processing
- `inprogress` — file is currently being processed
- `disabled` — AI processing is disabled for this file
- `failed` — AI processing failed for this file

**`_context` — Contextual metadata:**

Certain responses include `_context` with additional metadata specific to the operation. For example, `comment` action
`add` responses include `anchor_formats` describing supported anchor types for positioning comments on files (image
regions, video/audio timestamps, PDF pages).

---

## URL Structure & Link Construction

Fastio uses subdomain-based routing. Organization domains become subdomains, and every resource (workspace, folder,
file, share) has a URL-safe identifier from API responses that you use to build links.

### How Org Domains Become Subdomains

When you create an organization, you choose a `domain` (2-80 characters, lowercase alphanumeric and hyphens). This
becomes the subdomain for all org URLs:

Organization domain: `"acme"` → All org URLs live at: `https://acme.fast.io/...`

The base domain `go.fast.io` is used for routes that don't require org context (public shares, auth).

> **Prefer `web_url`.** The URL patterns below are reference material. In practice, always use the `web_url` field from tool responses — it handles subdomain routing, slug generation, and edge cases automatically. Only fall back to manual construction when `web_url` is absent (e.g., share-context storage operations).

### Building URLs From API Responses

Every URL parameter comes from a field in the API response. You never need to generate or guess identifiers — use the
values the API gives you.

| URL Parameter | API Response Field            | Format                                | Example                               |
|---------------|-------------------------------|---------------------------------------|---------------------------------------|
| Subdomain     | `organization.domain`         | User-chosen slug                      | `acme`                                |
| Workspace     | `workspace.folder_name`       | URL-safe slug                         | `q4-planning`                         |
| Folder        | `folder.id` (storage node ID) | Opaque ID, or `root` / `trash`        | `2rii2hzajpc2s3kce3itd2z5esygv`       |
| File          | `file.id` (storage node ID)   | Opaque ID                             | `2xzvfaq3slqwa54qtezi66rrcly6w`       |
| Share         | `share.custom_name`           | Server-generated identifier           | `abc123xyz`                           |
| QuickShare    | `quickshare.id`               | Server-generated identifier           | `qs-abc123xyz`                        |

### Deep Links Into Workspaces

These URLs require the user to be logged in and a member of the org. Use the org's `domain` as the subdomain and the
workspace's `folder_name` as the workspace identifier.

| Link Type          | URL Pattern                                                                  |
|--------------------|-----------------------------------------------------------------------------|
| Workspace root     | `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/storage/root` |
| Specific folder    | `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/storage/{folder.id}` |
| File preview       | `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/preview/{file.id}` |
| AI chat            | `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/storage/root?chat={chat_id}` |
| Note (in workspace)| `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/storage/root?note={note_id}` |
| Note (preview)     | `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/preview/{note_id}` |
| Browse workspaces  | `https://{org.domain}.fast.io/browse-workspaces`                            |

#### Workspace View Query Parameters

Append query parameters to workspace storage URLs to control the initial view mode and info panel tab:

| Parameter | Values                                              | Effect                              |
|-----------|-----------------------------------------------------|-------------------------------------|
| `view`    | `list`, `grid`, `metadata`                          | Sets file list layout mode          |
| `tab`     | `info`, `metadata`, `comments`, `activity`, `versions` | Opens info panel on specified tab |

Parameters are applied on page load only — they set the initial view state but are not updated during in-app navigation.

**Examples:**
- `https://acme.fast.io/workspace/q4-planning/storage/root?view=metadata`
- `https://acme.fast.io/workspace/q4-planning/storage/root?tab=metadata`
- `https://acme.fast.io/workspace/q4-planning/storage/root?view=metadata&tab=info`

**Examples:**
- `https://acme.fast.io/workspace/q4-planning/storage/root`
- `https://acme.fast.io/workspace/q4-planning/storage/2rii2hzajpc2s3kce3itd2z5esygv`
- `https://acme.fast.io/workspace/q4-planning/preview/2xzvfaq3slqwa54qtezi66rrcly6w`

### Shareable Links (No Auth Required)

These are the URLs you send to humans. Access depends on share settings, not authentication.

| Link Type              | URL Pattern                                                                        |
|------------------------|-----------------------------------------------------------------------------------|
| Public share           | `https://go.fast.io/shared/{share.custom_name}/{title-slug}`                      |
| Org-branded share      | `https://{org.domain}.fast.io/shared/{share.custom_name}/{title-slug}`            |
| File within a share    | `https://go.fast.io/shared/{share.custom_name}/{title-slug}/preview/{file.id}`    |
| QuickShare             | `https://go.fast.io/quickshare/{quickshare.id}`                                   |

The `{title-slug}` is the share title converted to a URL slug (lowercase, spaces to hyphens, special chars removed).
It's optional — routing works with just the `custom_name` — but improves link readability.

**Examples:**
- `https://go.fast.io/shared/abc123xyz/q4-financial-report`
- `https://acme.fast.io/shared/abc123xyz/q4-financial-report`
- `https://go.fast.io/shared/abc123xyz/q4-financial-report/preview/2xzvfaq3slqwa54qtezi66rrcly6w`
- `https://go.fast.io/quickshare/qs-abc123xyz`

### Share Management Links (For Owners/Admins)

| Link Type                    | URL Pattern                                                                          |
|------------------------------|--------------------------------------------------------------------------------------|
| Edit share (from workspace)  | `https://{org.domain}.fast.io/workspace/{workspace.folder_name}/share/{share.custom_name}` |
| Edit share (direct)          | `https://{org.domain}.fast.io/share/{share.custom_name}`                             |

### Settings & Account Links

| Link Type   | URL Pattern                                                                          |
|-------------|--------------------------------------------------------------------------------------|
| Org settings| `https://{org.domain}.fast.io/settings`                                              |
| Billing     | `https://{org.domain}.fast.io/settings/billing`                                      |
| Onboarding  | `https://go.fast.io/onboarding` or `https://go.fast.io/onboarding?orgId={org.id}&orgDomain={org.domain}` |

### Typical Agent Flow: Create and Link

1. **Create org** → API returns `org.domain` (e.g., `"acme"`)
2. **Select a paid plan** → activates the org so it can do work (Starter, Business, or Growth)
3. **Create workspace** → API returns `workspace.folder_name` (e.g., `"client-docs"`)
4. **Upload files to folder** → API returns `file.id` for each file
5. **Create share from folder** → API returns `share.custom_name`
6. **Build links for the human:**
   - Workspace link: `https://acme.fast.io/workspace/client-docs/storage/root`
   - Share link: `https://go.fast.io/shared/{custom_name}/client-docs`
   - File link: `https://go.fast.io/shared/{custom_name}/client-docs/preview/{file.id}`

---

## Signing / E-Signature

The platform ships a native e-signature surface so an agent can collect legally enforceable signatures on PDFs without
leaving the workspace. A SignEnvelope is an audit-archive Profile holding up to 20 PDFs sent to one or more recipients;
the internal PAdES-LT engine produces a long-term-validation cryptographic signature on every completed document and
the envelope's audit certificate captures the full chain of evidence (consent acceptance, OTP authentication where
required, per-recipient sign events, document hashes).

> **Full reference:** [https://api.fast.io/current/llms/signing/](https://api.fast.io/current/llms/signing/)
> **Public HTML docs:** [https://api.fast.io/current/docs/signing/](https://api.fast.io/current/docs/signing/)

### What an Agent Gets From Signing

| Capability | What It Solves |
|------------|----------------|
| **Native PDF signing** | No external e-sign account required for the default path. The platform's internal PAdES-LT engine embeds a long-term-validation signature directly in the completed PDF; the signature is verifiable offline by any standard PDF reader. |
| **Audit certificate** | Every completed envelope ships with a downloadable audit certificate (JSON evidence record) capturing the per-recipient consent, OTP flow, sign timestamps, and document hashes. The chain underneath it is hash-linked and HMAC-signed. |
| **Recipient OTP** | Configure `auth_method=email_otp` or `sms_otp` on a recipient and the signer surface gates the `/sign` action behind a 6-digit OTP. Code issuance and verification are independently throttled per `(envelope, recipient)` so the brute-force surface stays bounded. |
| **Sequential or parallel routing** | Recipients carry a 1-based `routing_order`. Distinct numbers fire sequentially; identical numbers fire in parallel. The platform activates the first slot on `/send/` and progresses through the slots as each recipient signs. |
| **Sign templates** | Capture a reusable signing configuration (recipient slots, document slots, field placements, policy) as a `SignTemplate` (`sa…` OpaqueId). Instantiate the template to produce a draft envelope with concrete bindings applied. |
| **First-view billing** | Credits are reserved at `/send/` and the first-view charge fires the first time any recipient opens the `/view` landing. Subsequent views by the same or different recipients don't re-charge — the meter is once-per-envelope. Voiding doesn't refund. |

### When to Reach for Signing

- An agent has produced a document that needs a human signature before it can ship.
- A contract / SOW / NDA / consent form needs to be collected and archived with a verifiable audit trail.
- A multi-party agreement needs sequential or parallel signing with per-recipient OTP gating.

If none of the above apply, an agent can still ship a PDF as a regular file via storage or a share — but the audit
chain and OTP gate only exist on the SignEnvelope surface.

### Concept Map (for AI agents)

| Concept | Type | Description |
|---------|------|-------------|
| **SignEnvelope** | profile (19-digit id) | The audit-archive entity. Parented to a Workspace. Carries lifecycle state (`draft` / `sent` / `in_progress` / `completed` / `declined` / `voided` / `expired` / `failed`), policy, and timestamps. |
| **SignTemplate** | OpaqueId (`sa…` prefix, 30-char) | A reusable signing configuration capturing recipient slots, document slots, field placements, and policy. Instantiate to produce a draft envelope. Soft-deleted (tombstoned), never purged. |
| **Document** | OpaqueId | One PDF inside the envelope. Up to 20 per envelope. Carries `source_node_id` / `source_version_id` (the storage node it was copied from), `signed_pdf_node_id` (set when signing completes), `source_sha256`, `completed_sha256`, `display_order`, `signed_at`. |
| **Recipient** | OpaqueId | One signer / cc / viewer / approver / certified-recipient on the envelope. Carries `role`, `routing_order`, `auth_method` (`none` / `email_otp` / `sms_otp`), and per-recipient lifecycle timestamps. Status flows `pending` → `sent` → `viewed` → `authenticated` → `signing_in_progress` → `signed` (with `declined` / `expired` / `voided` / `failed` terminal). |
| **Field** | OpaqueId | A field placement on a `(document_id, page)`. Normalized `0..1` coordinates. Type is one of `signature` / `initial` / `date` / `text` / `checkbox`. Belongs to exactly one recipient. |
| **Signer token** | compact JWT | Short-lived path-token JWT bound to a `(envelope_id, recipient_id)`. The recipient's signing link is `/sign_envelopes/signer/{token}/view/`. The token is consumed (single-use) on the state-changing actions (`/sign`, `/decline`, OTP-verify). |
| **Audit certificate** | OpaqueId (node) | The per-envelope audit certificate (JSON evidence record), generated when the envelope reaches a terminal state (completed, voided, or declined). `audit_certificate_node_id` on the envelope resource goes non-null when the certificate is in place; both the owner `/audit/download/` and signer `/sign_envelopes/{token}/audit/download/` endpoints stream the JSON bytes directly (no read-token round-trip). |
| **Activity events** | event type | `sign_envelope_drafted`, `sign_envelope_sent`, `sign_envelope_voided`, `sign_envelope_viewed`, `sign_envelope_recipient_signed`, `sign_envelope_recipient_declined`, `sign_envelope_document_signed`, `sign_envelope_completed`, `sign_envelope_expired`. Visible through `/events/search/` and outbound webhook subscriptions. |

**ID format note:** Envelope id is **19-digit numeric** (profile id format). Document / recipient / field / node ids
are **OpaqueIds** in hyphenated form (e.g. `f3jm5-zqzfx-pxdr2-dx8z5-bvnb3-rpjf`). Sign template ids are 30-character
`sa`-prefixed OpaqueIds. Signer tokens are compact JWTs and must never be persisted longer than the envelope's lifetime.

### Auth Model

| Endpoint family | Auth |
|------------------|------|
| Sender / admin (workspace-parented) | `Authorization: Bearer {api_key}` (JWT / OAuth / API key) |
| Signer surface | signer session token carried in the URL path — no session required |
| Provider webhook receiver | Provider's HMAC signature header — no Fastio session |

Workspace **view** is required for read endpoints; workspace **admin** for the
mutating endpoints (`/send`, `/void`, document downloads on a signed envelope, audit download). Signing availability
depends on your organization's plan and enabled features.

### Pattern Cookbook

Every signing flow an agent typically runs reduces to one of these recipes. All examples assume an API key in
`{api_key}` and a workspace id in `{workspace_id}`.

#### Pattern 1: Send a one-recipient envelope, wait for completion

```bash
# 1. Create the draft envelope.
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/create/" \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "expires_at": "2026-06-15 14:30:00 UTC",
    "policy_json": {"auth_method": "email_otp"},
    "documents": [
      {"source_node_id": "{source_node_id}", "source_version_id": "{source_version_id}", "display_order": 0}
    ],
    "recipients": [
      {"email": "signer@example.com", "display_name": "Alex Signer", "role": "signer", "routing_order": 1, "auth_method": "email_otp"}
    ],
    "fields": [
      {"recipient_email": "signer@example.com", "document_index": 0, "page": 1, "x_norm": 0.5, "y_norm": 0.8, "w_norm": 0.2, "h_norm": 0.05, "type": "signature", "required": true}
    ]
  }'
# -> 200 OK { "sign_envelope": { "id": "{envelope_id}", "envelope_status": "draft", ... } }

# 2. Send the envelope. The platform reserves credits, transitions Draft -> Sent, and dispatches notification.
curl -X POST "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/send/" \
  -H "Authorization: Bearer {api_key}"
# -> 200 OK { "sign_envelope": { "envelope_status": "sent", "sent_at": "...", ... } }

# 3. Poll the envelope until terminal. Drive on `envelope_status`; when it goes to `completed`, the audit certificate
#    is rendered shortly after and `audit_certificate_node_id` goes non-null.
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/details/" \
  -H "Authorization: Bearer {api_key}"

# 4. (After completion) Download the signed PDF and the audit certificate.
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/documents/{document_id}/signed/download/" \
  -H "Authorization: Bearer {api_key}"
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/audit/download/" \
  -H "Authorization: Bearer {api_key}"
```

#### Pattern 2: Catch a void or decline, react accordingly

```bash
# Subscribe to the relevant activity events via an outbound webhook subscription with event types
# `sign_envelope_voided`, `sign_envelope_recipient_declined`, or `sign_envelope_completed`.
#
# When the event lands, look up the envelope's current state via:
curl -X GET "https://api.fast.io/current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/details/" \
  -H "Authorization: Bearer {api_key}"
# -> envelope.envelope_status is one of `voided` / `declined` / `completed` / `expired` / `failed`.
# -> envelope.voided_reason carries the operator's void reason; recipient[].decline_reason carries the signer's reason.
```

### What to Tell the Recipient

The signer surface is path-token authenticated. The notification email the platform sends contains a link of the
form `/sign_envelopes/signer/{token}/view/`. The recipient's flow is:

1. **Click the link** → renders the envelope state, documents, fields, and consent disclosure via `GET /sign_envelopes/signer/{token}/view/`.
2. **Authenticate (OTP recipients only)** → enter the 6-digit code that the platform sent. The verify response carries a new elevated token; the client uses it for the rest of the flow.
3. **Review and accept consent**, **fill in field values**, **click sign** → `POST /sign_envelopes/signer/{token}/sign/` submits the values; the platform queues the async PAdES-LT signing job and returns a polling token.
4. **Wait for completion** → the client polls `/status/` and respects the adaptive `next_poll_seconds` (2s for the first 10s after sign, 5s for the next 60s, 15s thereafter).
5. **Or decline** → `POST /sign_envelopes/signer/{token}/decline/` with an optional reason cascades the envelope to `declined`.

Path tokens are **single-use** on the consume-style actions (`/sign`, `/decline`, OTP verify). The landing token works
multiple times on `/view` and `/status` until it has been consumed by one of the one-shot actions.

### Gotchas

- **Signing availability is plan-dependent.** Check your organization's plan and enabled features to confirm signing access.
- **Voiding doesn't refund.** Credits are consumed at `/send/`; the void path captures a reason and short-circuits
  pending recipients but does not refund. This matches industry convention.
- **A single decline kills the envelope.** Pending recipients in later routing slots never get notified once the
  envelope cascades to `declined`. Plan around this for sequential multi-signer flows.
- **The signed PDF endpoint 404s until the document completes.** Drive on the document's `signed_at` timestamp on the
  envelope resource — when it's non-null, the signed PDF is downloadable.
- **The audit certificate 404s until the envelope completes.** Drive on `audit_certificate_node_id` on the envelope
  resource — it goes non-null when the certificate is ready.
- **OTP throttles are per-(envelope, recipient).** Three OTP issuances per 15 minutes; fifteen verify attempts per
  15 minutes. The verify throttle protects against brute-force across multiple issuances.
- **Path tokens are short-lived.** Don't store the signer token longer than the envelope's lifetime. The OTP-elevation
  path re-mints the token; outside that, treat it as single-use on consume actions.
- **Documents per envelope is capped at 20.** Exceeding it is rejected at create time with `1605 (Invalid Input)`.
- **PATCH only works on draft envelopes.** A Sent / InProgress / terminal envelope is immutable on the sender surface;
  the only transitions are through `/void` and the signer-surface actions.

### See Also

- Full LLM reference: [https://api.fast.io/current/llms/signing/](https://api.fast.io/current/llms/signing/)
- Public HTML docs: [https://api.fast.io/current/docs/signing/](https://api.fast.io/current/docs/signing/)

---

## Per-Workspace Dashboard

The Dashboard API surfaces a ranked, paginated feed of **actionable cards** for each workspace member — pending signatures, @mentions, and file activity — in a single endpoint call. When the workspace plan includes AI features, an AI overlay adds urgency scores (0–100), AI-generated summaries, and suggested actions to each card, and may append cross-item synthesis cards at the end of the feed.

**Key characteristics:**

- **Per-workspace, per-member.** A member sees only the cards relevant to them in that workspace.
- **AI overlay is additive and gracefully degrades.** Cards are always useful without AI; when AI is available, `urgency`, `ai_summary`, and `suggested_action` enrich each card. Synthesis cards (type `synthesis`, source `ai`) only appear when AI is available.
- **Ripley Agent seed.** Every card carries a `ripley_seed` — a pre-populated question plus typed entity subjects — for launching a focused Ripley Agent conversation about that card.
- **Dismiss/snooze is out-of-band.** Dismissing or snoozing a card hides it from the member's view only; the underlying signature is unaffected.
- **Blocking cards surface first.** Cards that gate other participants are ranked ahead of non-blocking ones.

### Card types

| Type | What it represents |
|------|--------------------|
| `signature` | A pending signature on an envelope |
| `mention` | An @-mention of the caller in a comment |
| `file_version` | A new version of a workspace file |
| `file_added` | A new file added to the workspace |
| `synthesis` | An AI-generated cross-item summary (source: `ai`) |

### Recommended agent workflow

```bash
# 1. Fetch the member's dashboard (first page)
GET /current/workspace/{workspace_id}/dashboard/?limit=50
Authorization: Bearer {jwt_token}

# 2. Render each card. For signature cards, use primary_action.endpoint to get the signing link:
POST /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/my_sign_link/
# → Returns: sign_url (when actionable), is_terminal, blocked_signers, etc.

# 3. Offer dismiss/snooze on dismissible cards. URL-encode card_key (contains ':').
POST /current/workspace/{workspace_id}/dashboard/cards/{card_key}/dismiss/
# Optional body: {"snooze_until": "2026-06-18 09:00:00 UTC"}

# 4. Pass ripley_seed to the Ripley Agent to pre-focus the conversation
# ripley_seed = {"question": "...", "subjects": [{type, id, display_text}]}
```

### Key response fields

| Field | Type | Description |
|-------|------|-------------|
| `card_key` | string | Stable card ID. URL-encode when in a path (contains `:`). |
| `type` | string | Card type (see table above). |
| `source` | string | `event`, `signature`, or `ai`. |
| `blocking` | boolean | True = blocks other participants; ranked first. |
| `due_at` | string or null | UTC due date (`"YYYY-MM-DD HH:MM:SS UTC"`). |
| `ai_summary` | string or null | AI-generated summary. `null` when AI overlay absent. |
| `urgency` | integer or null | AI urgency 0–100. `null` = not evaluated. |
| `suggested_action` | string or null | AI-suggested next action. `null` when AI overlay absent. |
| `ripley_seed` | object | `{question, subjects: [{type, id, display_text}]}` for Ripley Agent. |
| `dismissible` | boolean | Whether the caller can dismiss/snooze this card. |
| `primary_action` | object | `{kind, method, endpoint, payload_template}` — the main action to take. |

### Authentication requirements

- `GET /current/workspace/{workspace_id}/dashboard/` — JWT, workspace View-or-above.
- `POST/DELETE /current/workspace/{workspace_id}/dashboard/cards/{card_key}/dismiss/` — JWT, workspace View-or-above.
- `POST /current/workspace/{workspace_id}/sign_envelopes/{envelope_id}/my_sign_link/` — JWT, **write-scope token** (minting a signing link is a state-changing operation).

> **Full reference:** [https://api.fast.io/current/llms/dashboard/](https://api.fast.io/current/llms/dashboard/)
> **Public HTML docs:** [https://api.fast.io/current/docs/dashboard/](https://api.fast.io/current/docs/dashboard/)

