# How to Upload Files Directly from the Browser to Fastio

Open a Receive share, mint a guest token with POST /current/share/{share_id}/auth/guest/, then POST the file from the browser to https://api.fast.io/current/upload/ as multipart form data (name, size, chunk, action=create, instance_id, folder_id). Your application server stays off the byte path. Confirm the file with GET /current/events/search/ or GET /current/activity/poll/{entityId}, then let Ripley and MCP tools work from the same workspace.

Source: https://fast.io/resources/fastio-api-presigned-urls-client-uploads/
Last reviewed: 2026-02-24

## Understanding the Bottleneck of Traditional Uploads

Traditional file upload architectures rely on the application server acting as an intermediary proxy. When a user uploads a document, video, or large dataset, the file travels from their browser to your backend server, which then streams that exact same file to your final cloud storage destination. While this approach is simple to understand and implement, it creates a big bottleneck as your application grows.

Proxying files consumes backend resources that should be reserved for business logic. Every active upload ties up server memory and holds open long-running network connections. It also burns through CPU cycles just to move bytes from network sockets. If multiple users attempt to upload large files concurrently, your server can easily exhaust its connection pool or run out of memory. This can cause the entire application to crash for every user on the system.

The financial cost is also an issue. This architecture forces you to pay for bandwidth twice. You pay once when the file data enters your infrastructure from the client, and you pay again when your server pushes the file to the storage layer. For data-heavy applications, these transfer costs add up fast. To solve this, keep the permanent Fastio API key on your server, hand the browser a guest credential for a Receive share, and let the browser POST the bytes straight to Fastio.

Helpful references: [Fastio Workspaces](/product/workspaces/), [Fastio Collaboration](/product/collaboration/), and [Fastio AI](/product/ai/).

## What Is a Fastio Guest Token?

A Fastio guest token lets a browser upload into a Receive share without ever seeing your permanent API key. Your backend (or the branded Receive portal) calls `POST https://api.fast.io/current/share/{share_id}/auth/guest/`, then the browser uses that token as `Authorization: Bearer` on the normal upload route. The permanent API key never leaves your server.

Create the Receive share first with `POST https://api.fast.io/current/workspace/{workspace_id}/create/share/`. Send, Receive, and Exchange shares are the three branded portal modes: Receive is the upload-only door you want for client-side intake. Read the share back with `GET https://api.fast.io/current/share/{share_id}/details/`. After the guest token is issued, the browser follows the documented upload flow: `POST https://api.fast.io/current/upload/` as `multipart/form-data` with `name`, `size`, `chunk` (the bytes), `action=create`, `instance_id` (the workspace ID), and `folder_id=root`. A small file returns HTTP 201 with `result`, `id`, and `new_file_id`.

This path matters for applications that deploy AI agents at scale. Fastio is an intelligent workspace, not a bare object bucket. Once the file lands, Ripley (the built-in RAG agent) and the MCP tools your agents already use can read it in place. Named mode includes `upload`, `storage`, `find`, `ai`, `share`, and `event`. Humans can also drop files into the same Receive portal in the browser, so agents and people share one intake path.

## Evidence and Performance Benchmarks

According to the AWS Compute Blog, direct client uploads can reduce application server bandwidth costs by 100% for file operations. Because the file data bypasses your application server, your backend infrastructure only handles the lightweight requests required to open the Receive share, mint the guest token, and confirm the file later. It no longer has to process the megabytes or gigabytes of actual file data.

In practical terms, this means an application server that previously struggled to handle multiple large video uploads can easily manage many more concurrent uploads. The backend merely orchestrates the guest slot, while the heavy lifting of moving data is offloaded to Fastio. This architectural shift improves scalability and reduces latency for the end user. It also stabilizes your core application performance under heavy load.

## The Direct Upload Architecture

Implementing direct client-side uploads requires coordinating state between your frontend application, your backend server, and the Fastio API. The process follows a strict multi-step sequence to maintain security while ensuring good performance.

- **Request a guest slot from your backend**: The client application signals its intent to upload a file by sending the file's metadata, such as its name and size, to your backend server. The backend authenticates the user and validates their permissions, then calls `POST https://api.fast.io/current/share/{share_id}/auth/guest/` on a Receive share you already created with `POST https://api.fast.io/current/workspace/{workspace_id}/create/share/`.
- **Upload the file from the frontend**: The backend returns the guest credential to the client. The browser then executes a direct `POST` to `https://api.fast.io/current/upload/` with the six multipart fields. This transfers the file data straight to Fastio.
- **Confirm the file on the backend**: Once the direct upload finishes, the client can pass `new_file_id` back to your application. Your backend can also watch the audit log with `GET https://api.fast.io/current/events/search/` or long-poll `GET https://api.fast.io/current/activity/poll/{entityId}?wait=95&lastactivity={timestamp}` until the file appears.

## How to Request a Guest Token from Your Backend

The first phase begins when the user selects a file in their browser or application. Before any file data moves over the network, your frontend must ask your backend for permission. Your backend server is the only component that holds your Fastio API key (`Authorization: Bearer {api_key}` against `https://api.fast.io/current/`) and has the authority to decide which Receive share the browser may write into.

Your backend endpoint should accept the requested file name and size from the client. It must first verify that the current user is authenticated and authorized to perform this specific action. Once authorized, the backend mints a guest token on the Receive share. Get an API key in the UI under Settings > Devices & Agents > API Keys, or with `POST /current/user/auth/key/`. Workspace and share IDs are 19-digit numeric strings.

```javascript
// Example Backend Implementation (Node.js)
app.post('/api/start-client-upload', async (req, res) => {
  const { filename, size } = req.body;

// Verify user authentication here
  // Validate file type and size restrictions
  if (size > MAX_FILE_SIZE) {
    return res.status(400).json({ error: 'File exceeds size limit' });
  }

try {
    const response = await fetch(
      'https://api.fast.io/current/share/' + process.env.FASTIO_RECEIVE_SHARE_ID + '/auth/guest/',
      { method: 'POST' }
    );

if (!response.ok) {
      return res.status(502).json({ error: 'Failed to open a guest upload slot' });
    }

// Forward the guest credential body as-is. Do not expose FASTIO_API_KEY.
    res.status(response.status).type('application/json').send(await response.text());
  } catch (error) {
    res.status(500).json({ error: 'Failed to open a guest upload slot' });
  }
});
```

Keep the guest token short-lived in your own session logic. If a credential is intercepted, it is scoped to that Receive share rather than to your permanent API key. Create a fresh guest token for the next upload instead of reusing one across users.

## How to Upload Files Directly from the Frontend

Once the frontend receives the guest credential, it can begin the actual data transfer. The browser posts `multipart/form-data` to `https://api.fast.io/current/upload/` and sends the guest token as `Authorization: Bearer`. Let the browser set the multipart `Content-Type` (including the boundary). Do not send a JSON body on this call. Most other Fastio POST bodies are `application/x-www-form-urlencoded`; uploads are the multipart exception.

Small files go in one request. Send `name`, `size`, `chunk` (the file bytes), `action=create`, `instance_id` (the workspace ID), and `folder_id=root`. HTTP 201 returns `{"result":true,"id":"<upload_id>","new_file_id":"<node_id>"}`. Keep `new_file_id` for later reads, shares, and Ripley queries. A same-name upload into the same folder overwrites in place and keeps the old content as a recoverable version. The `node_id` stays stable.

```javascript
// Example Frontend Implementation (Browser)
async function uploadFileDirectly(file, token, workspaceId) {
  const form = new FormData();
  form.append('name', file.name);
  form.append('size', String(file.size));
  form.append('chunk', file);
  form.append('action', 'create');
  form.append('instance_id', workspaceId);
  form.append('folder_id', 'root');

const uploadResponse = await fetch('https://api.fast.io/current/upload/', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token
    },
    body: form
  });

if (!uploadResponse.ok) {
    throw new Error('Direct upload failed');
  }

const data = await uploadResponse.json();
  return data.new_file_id;
}
```

For a larger file, use the chunked session on the same host. POST the same form without `chunk` to `https://api.fast.io/current/upload/` and read `{id}`. Then `POST /current/upload/{id}/chunk/?order=N&size=N` with multipart field `chunk` (HTTP 202), `POST /current/upload/{id}/complete/` (HTTP 202), and `GET /current/upload/{id}/details/?wait=60` for `{session:{status,new_file_id}}`. Batch intake of many small files is `POST /current/upload/batch/` (up to 200 files, each 4MB or smaller). A remote URL can skip the browser entirely with `POST /current/web_upload/` (`source_url`, `file_name`, `profile_id`, `profile_type` set to `workspace` or `share`, and `folder_id`).

## How to Notify the Backend and Trigger Workflows

The final step connects the isolated file upload to your application's core business logic. Because the file went directly to Fastio, your backend server does not automatically see the bytes land. You need a reliable way to learn the new `node_id` and continue your own processing.

The most straightforward approach is client-side confirmation. After the upload `POST` returns HTTP 201, the frontend makes a final call to your backend and passes `new_file_id`. Your backend can then update its database and link the file to the correct user profile.

Client-side confirmation can miss the event if the user closes the tab right after Fastio accepts the file. For production apps, also watch Fastio activity from the server. Search the audit log with `GET https://api.fast.io/current/events/search/`, or long-poll `GET https://api.fast.io/current/activity/poll/{entityId}?wait=95&lastactivity={timestamp}`. When a new `node_id` appears, start Ripley with `POST /current/workspace/{workspace_id}/ai/agent/` and send a message with `POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/message/`. Agents on MCP can do the same through the `event` tool and the `ai` tool (`ask`).

## Handling Advanced Edge Cases: CORS and Custom Metadata

When implementing direct uploads, development teams frequently encounter challenges with browser security models and metadata tracking. The upload call is a cross-origin `POST` of `multipart/form-data` to `https://api.fast.io/current/upload/`. Send only the documented form fields and the `Authorization: Bearer` guest token. Extra JSON fields in the body are not part of the upload contract, and a hand-set `Content-Type: application/json` will not match the multipart form.

Structured metadata lives on the file after it exists, not on the upload POST. Create a template with `POST /current/workspace/{workspace_id}/metadata/templates/`, then extract fields from a stored file with `POST /current/workspace/{workspace_id}/storage/{node_id}/metadata/extract/`. You can also run `POST /current/workspace/{workspace_id}/metadata/templates/{template_id}/extract-all/`, list views with `GET /current/workspace/{workspace_id}/metadata/views/`, and export a view with `POST /current/workspace/{workspace_id}/metadata/view/{template_id}/export/`. Agents can ask Ripley about those files through MCP `ai` (`ask`) once the `node_id` is in the workspace.

Keep trailing slashes on every Fastio path. Profile IDs are 19-digit numeric strings. HTTP 429 with error code 1671 means rate limited; back off until the `x-ve-limit-expires` header. Other documented codes include 1605 Invalid Input, 1609 Not Found, 1650 Auth Invalid, 1680 Access Denied, 1685 Feature Limit, and 1654 Internal Error.

## Fastio: The Ideal Intelligent Workspace for Developers

Implementing direct uploads solves the bandwidth problem, but where those files land matters even more. Fastio is designed as an intelligent workspace, not just basic commodity storage. It serves as the active coordination layer where agent output becomes real team output.

When the browser posts into a Receive share, you drop files into an environment that Ripley and your agents can query. Humans use the web interface and branded Send, Receive, or Exchange portals. Agents use the MCP server at `https://mcp.fast.io/mcp` (use `https://mcp.fast.io/mcp/key` with a Bearer header; legacy SSE is `https://mcp.fast.io/sse`). Named mode exposes a consolidated MCP toolset, including `upload`, `storage`, `find`, `ai`, and `share`. Code mode for headless agents exposes tools including `auth`, `upload`, `search`, `execute`, `room`, and `how-to`.

A tools/call looks like this:

```json
{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"upload","arguments":{"action":"web-import","url":"https://example.com/report.pdf",
 "profile_type":"workspace","profile_id":"1234567890123456789"}}}
```

Verified `upload` actions include `web-import`, `stream-upload`, `create-session`, `chunk`, `finalize`, `batch`, `limits`, and `blob-info`. Agents connect to the remote MCP server URL at https://mcp.fast.io/mcp. Agents can create organizations, open workspaces, and hand finished files to people through a Send share or a durable fileshare (`POST /current/workspace/{workspace_id}/create/fileshare/`). That is the production path for client-side intake into an agent workspace.

## Frequently asked questions

### How do Fastio guest tokens work?

Your backend calls POST https://api.fast.io/current/share/{share_id}/auth/guest/ on a Receive share and hands the browser that guest token. The browser then POSTs multipart form data to https://api.fast.io/current/upload/ with Authorization: Bearer and fields name, size, chunk, action=create, instance_id, and folder_id.

### How to upload large files directly from browser?

Mint a guest token on a Receive share, then open a chunked session: POST https://api.fast.io/current/upload/ without the chunk field to get {id}, POST /current/upload/{id}/chunk/?order=N&size=N with multipart chunk (HTTP 202), POST /current/upload/{id}/complete/ (HTTP 202), and GET /current/upload/{id}/details/?wait=60 for session.status and session.new_file_id.

### Are guest tokens secure for public applications?

Yes, when the browser never sees your permanent API key. Keep the key on the server (Settings > Devices and Agents > API Keys, or POST /current/user/auth/key/). Issue a guest token scoped to a Receive share, and let the browser upload with that Bearer token only. Confirm the file later with GET /current/events/search/ or GET /current/activity/poll/{entityId}.

### How do I handle CORS errors during a client-side upload?

POST multipart/form-data from the browser to https://api.fast.io/current/upload/. Send Authorization: Bearer with the guest token and the six documented form fields. Let the browser set the multipart Content-Type, including the boundary. A JSON content type on that request will not match the upload form.

### What happens if an upload fails halfway through?

If a one-shot POST /current/upload/ is interrupted, send the same multipart fields again. For a large file, open a chunked session, POST each piece to /current/upload/{id}/chunk/?order=N&size=N, then POST /current/upload/{id}/complete/ and GET /current/upload/{id}/details/?wait=60. HTTP 429 with error code 1671 means back off until the x-ve-limit-expires header.

### Can AI agents access the files immediately after upload?

Yes. After POST /current/upload/ returns new_file_id, Ripley can read the file through POST /current/workspace/{workspace_id}/ai/agent/ and POST /current/workspace/{workspace_id}/ai/agent/{chat_id}/message/. On MCP, use the ai tool (ask) with profile_type, or upload with the upload tool (stream-upload, web-import, create-session, chunk, finalize).

## About Fast.io

Fast.io provides shared workspaces where people and AI agents work on the same files, with built-in semantic search and citation-backed chat over what they hold. Agents reach it through a remote MCP server at https://mcp.fast.io/mcp, a REST API at https://api.fast.io/current/, and a command line client published on npm as @vividengine/fastio-cli.
