# Floppy for agents

Floppy (https://fpy.sh) shares a file with a short link and shows who opened it: which pages they read and for how long, with bots filtered out. Agents can use it with no account. Upload with one HTTP call, or connect over MCP, and hand your human a claim link so the work becomes theirs.

- One call: `curl -T deck.pdf https://fpy.sh/api/drop/deck.pdf`
- MCP server: `https://fpy.sh/mcp` (Streamable HTTP)
- OpenAPI: https://fpy.sh/openapi.json
- How to get credentials: https://fpy.sh/auth.md
- This guide as plain Markdown: https://fpy.sh/docs/agents.md (also https://fpy.sh/llms-full.txt)

## Connect your agent

Pick the client you use. Each one takes about two minutes and no code. Signing in uses your Google account. Prefer a key? Create one at https://fpy.sh/app/keys and use the key variant shown under each client.

### Claude (claude.ai and Claude Desktop)

1. In Claude, open **Customize → Connectors**, click **+ Add**, then **Add custom connector**.
2. Name it `Floppy` and paste this URL, then click **Add** and sign in with Google:

   ```text
   https://fpy.sh/mcp
   ```

   Using a key instead? Add a request header `Authorization: Bearer fpy_live_…` and choose "No sign in".
3. Test it. Paste this into a new chat:

   ```text
   Write a short Markdown note called hello.md, upload it to Floppy and give me the link.
   ```

Stuck? On a Team or Enterprise plan, an owner adds the connector first under **Organization settings → Connectors**.

### Claude Code

1. Open a terminal and run:

   ```sh
   claude mcp add --transport http floppy https://fpy.sh/mcp
   ```

   With a key: `claude mcp add --transport http floppy https://fpy.sh/mcp --header "Authorization: Bearer fpy_live_…"`
2. Start Claude Code and type `/mcp`. Pick `floppy` and sign in with Google. (With a key, skip this step.)
3. Test it. Paste this:

   ```text
   Upload README.md to Floppy and give me the link.
   ```

Stuck? Run `claude mcp list` to check that floppy is connected. Add `--scope user` to the first command to use Floppy in every project.

### ChatGPT

1. Open https://chatgpt.com/plugins, click **+**, then **Add custom MCP server**. If you don't see it, turn on **Developer mode** under **Settings → Apps & Connectors → Advanced** first.
2. Name it `Floppy`, paste this URL, choose OAuth, create it and sign in with Google:

   ```text
   https://fpy.sh/mcp
   ```

3. Test it. In a new chat, pick Floppy from the **+** menu and paste this:

   ```text
   Write a short Markdown note called hello.md, upload it to Floppy and give me the link.
   ```

Stuck? Uploading through ChatGPT needs a plan with full MCP access (Business, Enterprise or Edu at the time of writing). On other plans, use Floppy from an agent that can run code (see "Any other agent").

### Cursor

1. Open this link to install Floppy in one click:

   ```text
   cursor://anysphere.cursor-deeplink/mcp/install?name=floppy&config=eyJ1cmwiOiJodHRwczovL2ZweS5zaC9tY3AifQ==
   ```

   Or add this to `~/.cursor/mcp.json`:

   ```json
   {
     "mcpServers": {
       "floppy": { "url": "https://fpy.sh/mcp" }
     }
   }
   ```

   With a key, add `"headers": { "Authorization": "Bearer fpy_live_…" }` next to `"url"`.
2. Open **Cursor Settings → MCP**. Next to floppy, click **Connect** and sign in with Google. (With a key, skip this step.)
3. Test it. In Agent chat, paste this:

   ```text
   Upload README.md to Floppy and give me the link.
   ```

Stuck? Check that floppy shows a green dot in **Cursor Settings → MCP**. Restart Cursor after editing `mcp.json` by hand.

### Codex

1. Add this to `~/.codex/config.toml`:

   ```toml
   [mcp_servers.floppy]
   url = "https://fpy.sh/mcp"
   ```

   With a key, add `bearer_token_env_var = "FLOPPY_API_KEY"` under it and run `export FLOPPY_API_KEY=fpy_live_…` before starting Codex.
2. Sign in with Google:

   ```sh
   codex mcp login floppy
   ```

3. Test it. Start `codex` and paste this:

   ```text
   Upload README.md to Floppy and give me the link.
   ```

Stuck? Run `codex mcp list` to check that floppy is there.

### VS Code

1. Run this in a terminal:

   ```sh
   code --add-mcp '{"name":"floppy","type":"http","url":"https://fpy.sh/mcp"}'
   ```

   Or add this to `.vscode/mcp.json`:

   ```json
   {
     "servers": {
       "floppy": { "type": "http", "url": "https://fpy.sh/mcp" }
     }
   }
   ```

   With a key, add `"headers": { "Authorization": "Bearer fpy_live_…" }` next to `"url"`.
2. Open Copilot Chat in **Agent** mode. When VS Code asks, start the floppy server and sign in with Google.
3. Test it. Paste this:

   ```text
   Upload README.md to Floppy and give me the link.
   ```

Stuck? Run **MCP: List Servers** from the Command Palette and check that floppy is running.

### Any other agent

1. MCP clients: connect to `https://fpy.sh/mcp` (Streamable HTTP). Sign in with OAuth, or send the header `Authorization: Bearer fpy_live_…` with a key from https://fpy.sh/app/keys.
2. Anything that can make an HTTP call can upload without MCP:

   ```sh
   curl -T deck.pdf https://fpy.sh/api/drop/deck.pdf
   ```

   With a key, add `-H "Authorization: Bearer fpy_live_…"` so the upload lands in your account.
3. Test it. Paste this into your agent:

   ```text
   Read https://fpy.sh/llms.txt, then upload README.md to Floppy and give me the link.
   ```

Worked? You'll see the link in your chat and at https://fpy.sh/app (or at the claim link, if your agent uploaded without an account).

## Quickstart

No account, no key. Send the file's bytes with `PUT`:

```sh
curl -T deck.pdf https://fpy.sh/api/drop/deck.pdf
```

The filename at the end of the URL is the name people see. Optional query parameters: `expiry` (for example `1D`, `3D`) and `label` (for example `Sequoia – Jane`).

To keep every upload in one account, send the `agent_key` from your first response on every later call:

```sh
curl -T notes.md https://fpy.sh/api/drop/notes.md -H "Authorization: Bearer fpy_live_…"
```

Files over 100 MB don't fit in one request. Use the multipart flow (`POST /api/v1/links`, then `PUT` each part, then `POST …/complete`) with your key, or `upload_file` over MCP, which returns a multipart plan for big files.

## What you get back

```json
{
  "link": {
    "id": "q7f2m9x4k1ab",
    "name": "deck.pdf",
    "url": "https://fpy.sh/k3x9",
    "slug": "k3x9",
    "kind": "pdf",
    "size": 4404019,
    "status": "ready",
    "scan": "scanning",
    "created_at": "2026-11-06T12:03:00.000Z",
    "expires_at": "2026-11-09T12:03:00.000Z",
    "shares": 0,
    "views": 0,
    "via_agent": true
  },
  "share": null,
  "share_url": "https://fpy.sh/k3x9",
  "analytics_url": "https://fpy.sh/api/v1/links/<id>/analytics",
  "claim_url": "https://fpy.sh/claim/7f3c…",
  "agent_key": "fpy_live_…",
  "scan": "scanning",
  "limits": { "storageBytes": 1073741824, "maxFileBytes": 1073741824, "maxExpiryDays": 3, "maxLinks": 20, "expires_in_hours": 72 },
  "next": "Share the share_url. Your human can claim these files at https://fpy.sh/claim/7f3c… within 72 hours."
}
```

- `share_url` is the link to send. It works at once; while `scan` is `scanning`, visitors see "Checking file…" and the file opens when the scan is clean.
- `agent_key` is shown **once**. Save it and send it as `Authorization: Bearer …` on later calls so your uploads stay together. `claim_url` and `agent_key` only appear when Floppy created a new unclaimed account for you.
- `claim_url` is for your human. Give it to them with the link.
- `scan` is one of `uploading`, `scanning`, `clean`, `blocked` or `unscanned-large`.
- `next` is one plain sentence telling you what to do or tell your human. Every response has one.

## Claiming

An upload without an account goes into an **unclaimed account**. It lasts 72 hours. Tell your human, in your own words:

> I uploaded your file to Floppy: https://fpy.sh/k3x9. To keep it and see who opens it, claim it within 72 hours: https://fpy.sh/claim/7f3c…

- Opening the claim link means signing in with Google. Everything the agent made moves to that person's account: links, files, folders and view history. It works for a new or an existing Floppy account.
- The claim link works once and expires with the unclaimed account. Lost it? Call `GET /api/v1/claim` with your agent key, or the `get_claim_link` MCP tool.
- After the claim, your agent key keeps working, but it stays **limited to the links it created** (scope `own`). It can't see the human's other links. The human can widen it to the whole account at https://fpy.sh/app/keys.
- Nothing is claimed after 72 hours? The links stop working and every file is deleted.
- Unclaimed accounts can't pay. The claim page also offers "Claim and upgrade", which goes to checkout after sign-in.

## MCP

| | |
|---|---|
| URL | `https://fpy.sh/mcp` |
| Transport | Streamable HTTP |
| Auth | OAuth (sign in with Google; dynamic client registration), or `Authorization: Bearer fpy_live_…` with an agent key or API key |

Every tool returns its result plus a `next` hint.

| Tool | Input | What it does |
|---|---|---|
| `upload_file` | `{ name, text?, content_base64?, url?, expiry?, label? }`, exactly one source | Uploads a file and returns its link. Up to 25 MB inline, or a `url` up to 100 MB. Bigger files get a multipart plan back. |
| `create_share` | `{ link_id, label, expiry? }` | Makes a separate link for one recipient, such as "Sequoia – Jane", so you can see what each person read. |
| `update_link` | `{ link_id, expiry?, password?, downloads_allowed?, note?, name? }` | Changes a link's settings. |
| `revoke_share` | `{ share_id }`, or `{ link_id }` to revoke everything | Turns off one recipient's link, or the whole link. |
| `list_links` | `{ query?, limit? }` | Lists links, newest first. |
| `get_link_analytics` | `{ link_id }` | Who opened a link, pages reached, time per page, completion, and how many bot visits were filtered. |
| `get_viewer_timeline` | `{ share_id }` | One recipient's visits, page by page. |
| `who_viewed_since` | `{ since }` (ISO time or `24h`) | Everything that happened since a time, newest first. |
| `get_usage` | `{}` | Plan, storage used and limits. |
| `get_upgrade_link` | `{}` | A checkout link for your human (or the claim link, if unclaimed). |
| `get_claim_link` | `{}` | The claim link for an unclaimed account. |

## REST API

Base URL `https://fpy.sh`. Auth is `Authorization: Bearer fpy_live_…` (or the dashboard's session cookie). JSON in and out. The full schema is at https://fpy.sh/openapi.json.

| Method | Path | Access | Request | Response |
|---|---|---|---|---|
| PUT | `/api/drop/:filename` | none or key | Raw bytes, up to 100 MB. Query: `expiry`, `label` | `{ link, share, share_url, analytics_url, claim_url?, agent_key?, scan, limits, next }` |
| GET | `/api/v1/links` | read | | `{ links, next }` |
| POST | `/api/v1/links` | write | `{ files: [{ name, size, type? }], folderId? }` | `{ link, uploads: [{ fileId, partSize, parts }], next }` |
| PUT | `/api/v1/files/:id/parts/:n` | write | Raw part, up to 32 MiB | `{ partNumber, etag }` |
| POST | `/api/v1/files/:id/complete` | write | `{ parts: [{ partNumber, etag }] }` | `{ ok, linkReady, scan, next }` |
| POST | `/api/v1/links/from-url` | write | `{ url, name? }`, up to 100 MB | same as drop |
| GET | `/api/v1/links/:id` | read | | `LinkDetail` |
| PATCH | `/api/v1/links/:id` | write | `{ expiry?, password?, downloadsAllowed?, note?, name? }` | `LinkDetail` |
| DELETE | `/api/v1/links/:id` | write | | `{ ok, next }` (revokes the link) |
| GET | `/api/v1/links/:id/shares` | read | | `{ shares }` |
| POST | `/api/v1/links/:id/shares` | write | `{ label, expiry? }` | `Share` |
| PATCH | `/api/v1/shares/:id` | write | `{ label?, active? }` | `Share` |
| DELETE | `/api/v1/shares/:id` | write | | `{ ok }` |
| GET | `/api/v1/links/:id/analytics` | read | | `LinkAnalytics` |
| GET | `/api/v1/shares/:id/timeline` | read | | `{ share, visits }` |
| GET | `/api/v1/activity?since=<ISO>` | read | | `{ events }`, newest first |
| GET | `/api/v1/usage` | read | | `{ plan, claimed, used_bytes, limits, link_count, expires_at? }` |
| POST | `/api/v1/upgrade` | read | | `{ upgrade_url }`, or `{ claim_url, next }` if unclaimed |
| GET | `/api/v1/claim` | agent key (unclaimed) | | `{ claim_url, expires_at }` |
| POST | `/api/v/:slug/report` | none | `{ reason, details? }` | `{ ok }` |

API keys are created and revoked by a signed-in human at https://fpy.sh/app/keys (`/api/v1/tokens` takes a session, never a key). Keys have access `read`, `write` or both, and scope `account` (everything) or `own` (only the links the key created).

## Limits & errors

| Plan | Storage | Per file | Longest expiry | Links |
|---|---|---|---|---|
| Unclaimed (agent, no account) | 1 GB | 1 GB | 72 hours, then deleted | 20 |
| Free | 2 GB | 500 MB | 30 days | no limit |
| Pro | 1 TB | 50 GB | any, or never | no limit |

A single drop or inline upload is also capped at 100 MB per request; use multipart above that. Unclaimed accounts can upload safe file types only (see Safety).

Every error has the same shape. `next` says what to do, and `docs` links here:

```json
{
  "error": {
    "code": "limit_reached",
    "message": "This unclaimed account has used its 1 GB.",
    "claim_url": "https://fpy.sh/claim/7f3c…",
    "next": "Ask your human to claim this account at https://fpy.sh/claim/7f3c…. Claiming unlocks Free limits and lets them upgrade.",
    "docs": "https://fpy.sh/docs/agents#errors"
  }
}
```

| Status | Code | Means | What to do |
|---|---|---|---|
| 400 | `bad_request` | The request is malformed | Fix the field named in `message` |
| 401 | `unauthorized` | Missing or invalid key | Read https://fpy.sh/auth.md (the `WWW-Authenticate` header points there) |
| 402 | `limit_reached` | Over a plan limit | Unclaimed: give your human `claim_url`. Claimed: give them `upgrade_url` |
| 403 | `forbidden` | The key can't touch this link | An `own` key only sees links it created; ask your human to widen it |
| 404 | `not_found` | No such link, share or file | Check the id. Never guess share links |
| 409 | `conflict` | The thing changed or already exists | Fetch it again and retry |
| 413 | `limit_reached` | Too big for one request | Use the multipart flow in `next` |
| — | — | A file failed the safety check | The upload still returns 201, but `scan` becomes `blocked` (check `GET /api/v1/links/:id`). Don't re-upload the same file |
| 429 | `slow_down` | Too many requests | Wait `retry_after` seconds (also in the `Retry-After` header) |

## Safety

- **Every file is scanned before anyone but its owner can open it.** Uploads start as `scanning`; the share page shows "Checking file…" until the verdict is in. Blocked files are never served.
- **Executables and installers are blocked on every plan.** Unclaimed accounts can upload only PDF, Markdown and text, images, audio, video, CSV and JSON. No archives, HTML or SVG.
- **HTML and SVG are always downloaded**, never rendered as a web page on fpy.sh.
- **Bots are filtered.** Email security scanners, link previews and datacenter traffic are kept apart and never counted as views.
- **Share links are private.** They belong to the people who made them. Never guess, enumerate or crawl fpy.sh links.
- **Report abuse** with the Report link on any share page.

## Troubleshooting

- **401 on every call.** The header must be exactly `Authorization: Bearer fpy_live_…`, with one space and no quotes. Keys revoked at https://fpy.sh/app/keys stop working at once.
- **The client doesn't show Floppy's tools.** Restart the client after adding the server, then sign in again (`/mcp` in Claude Code, `codex mcp login floppy` in Codex, Connect in Cursor).
- **Sign-in loops or fails.** Use the Google account you want the files in. Blocked pop-ups stop the OAuth window; allow them for the client.
- **ChatGPT can read but not upload.** Your plan only allows read access to custom MCP servers. Use another agent, or the curl call.
- **The file is stuck on "Checking file…".** Scans usually take seconds; large files take longer. Poll `GET /api/v1/links/:id` and look at `scan`.
- **402 `limit_reached`.** Hand your human the `claim_url` or `upgrade_url` from the error. Paying or claiming lifts the limit for the same key at once.
- **413 on a drop.** The file is over 100 MB. Use the multipart flow.
- **I lost the agent key.** It can't be shown again. If the account is unclaimed, start a new drop; if it was claimed, your human can make a new key at https://fpy.sh/app/keys.
- **The claim link expired.** After 72 hours unclaimed uploads are deleted. Upload again and pass the new claim link on sooner.

## More pages

Setup for one tool at a time, with things to ask and an FAQ. Each page is also available as Markdown (add `.md`).

- [Share files from Claude Code](https://fpy.sh/agents/claude-code) ([Markdown](https://fpy.sh/agents/claude-code.md))
- [Share files from Claude](https://fpy.sh/agents/claude) ([Markdown](https://fpy.sh/agents/claude.md))
- [Share files from ChatGPT](https://fpy.sh/agents/chatgpt) ([Markdown](https://fpy.sh/agents/chatgpt.md))
- [Share files from Cursor](https://fpy.sh/agents/cursor) ([Markdown](https://fpy.sh/agents/cursor.md))
- [Share files from Codex](https://fpy.sh/agents/codex) ([Markdown](https://fpy.sh/agents/codex.md))
- [Share files from VS Code and GitHub Copilot](https://fpy.sh/agents/vs-code) ([Markdown](https://fpy.sh/agents/vs-code.md))
- [A file sharing MCP server](https://fpy.sh/agents/mcp) ([Markdown](https://fpy.sh/agents/mcp.md))

How Floppy compares with other ways to share files:

- [Floppy vs Papermark](https://fpy.sh/vs/papermark) ([Markdown](https://fpy.sh/vs/papermark.md))
- [Floppy vs DocSend](https://fpy.sh/vs/docsend) ([Markdown](https://fpy.sh/vs/docsend.md))
- [Floppy vs WeTransfer](https://fpy.sh/vs/wetransfer) ([Markdown](https://fpy.sh/vs/wetransfer.md))
- [Floppy vs Dropbox](https://fpy.sh/vs/dropbox) ([Markdown](https://fpy.sh/vs/dropbox.md))
- [Floppy vs Google Drive](https://fpy.sh/vs/google-drive) ([Markdown](https://fpy.sh/vs/google-drive.md))
- [Floppy vs Smash](https://fpy.sh/vs/smash) ([Markdown](https://fpy.sh/vs/smash.md))
- [Floppy vs Box](https://fpy.sh/vs/box) ([Markdown](https://fpy.sh/vs/box.md))
