# Pipe

**Page:** kit/pipe

[Download Raw Markdown](./kit/pipe.md)

---

# Pipe

**Named pipes, but over the internet.** Send data to a path via PUT/POST, receive it via GET — data streams in real-time from sender to receiver(s) with zero server-side storage. Any path. Any content. Up to 256 simultaneous receivers.

## Why This Matters

Sharing data between machines has always been painful. FTP servers, SSH tunnels, file upload services, WebSocket boilerplate, signaling servers for WebRTC. Every approach requires setup, authentication libraries, or specialized clients.

hoody-pipe reduces all of it to two curl commands:

```bash
# Terminal A: Send a file
curl -T report.pdf https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/my-report

# Terminal B: Receive the file (run this before, during, or after the sender connects)
curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/my-report > report.pdf
```

**That's it.** No upload limits. No temporary storage. Data flows directly from sender to receiver the moment both are connected.

**No per-pipe accounts — access control is enforced centrally by Hoody Proxy** (IP allowlist, passwords, JWT). The pipe service itself has no service-level auth; permissions are configured at the proxy layer, not per-pipe.

---

## How It Works

1. A sender **POSTs** (or **PUTs**) data to a path — e.g. `/api/v1/pipe/my-screen-share`
2. A receiver **GETs** the same path
3. Data streams directly from sender to all receivers — **no buffering, no temporary files**
4. Either party can connect first — the server holds the early connection until the counterpart arrives (up to 5-minute TTL)

The path is just a name you choose. `/my-file`, `/demo-stream`, `/logs/today` — anything that isn't a reserved system path (`/`, `/help`, `/noscript`, `/favicon.ico`, `/robots.txt`). Reserved-path matching is normalization-robust: `/Help`, `/help/`, `/help.`, `/help%2e` all fold to `/help` and are rejected, so attackers can't register an aliased pipe over a built-in page. The canonical health endpoint is `/api/v1/pipe/health`; the server also explicitly 404s a bare `/health` with the hint `'/health' is not a valid path. Use '/api/v1/pipe/health'.`


hoody-pipe is **not** store-and-forward. There is no upload step followed by a download step. Data flows through the server in real-time. When the sender writes a byte, receivers see it immediately.


---

## Quick Examples

### Send and Receive a File


  
    ```bash
    # Sender: Upload a file
    hoody pipe send backup ./backup.tar.gz --container $CONTAINER

    # Receiver: Download the file
    hoody pipe receive backup -o backup.tar.gz --container $CONTAINER
    ```
  
  
    ```bash
    # Sender: Upload a file (PUT is natural for curl -T)
    curl -T ./backup.tar.gz \
      https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/backup

    # Receiver: Download the file
    curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/backup \
      -o backup.tar.gz
    ```
  
  
    ```typescript
    import { HoodyClient, PipeStream } from '@hoody-ai/hoody-sdk';
    import { createReadStream, createWriteStream } from 'node:fs';
    import { Writable } from 'node:stream';

    const client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: process.env.HOODY_TOKEN });
    const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });

    // Generic byte-stream API (any source/sink) via PipeStream
    const pipe = PipeStream.fromClient(client, { id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });

    // Send: any PipeSource (file stream, Buffer, string, ReadableStream, TCP/Unix socket, AsyncIterable)
    const send = await pipe.send('backup', createReadStream('./backup.tar.gz'));
    await send.done;

    // Receive: get a Web ReadableStream you can pipe anywhere
    const recv = await pipe.receive('backup');
    await recv.body.pipeTo(Writable.toWeb(createWriteStream('./backup.tar.gz')));
    ```
  


### Stream Text Between Containers


  
    ```bash
    # Container A: stream a long-running command's stdout
    hoody pipe send live-logs --from-cmd "tail -f /var/log/app.log"

    # Container B: receive to stdout (the default sink)
    hoody pipe receive live-logs
    ```
  
  
    ```bash
    # Container A: Pipe logs in real-time
    tail -f /var/log/app.log | curl -T - \
      https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/live-logs

    # Container B: Watch the logs
    curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/live-logs
    ```
  
  
    ```typescript
    import { PipeStream } from '@hoody-ai/hoody-sdk';

    const pipe = PipeStream.fromClient(client, { id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });

    // Async-iterable source — perfect for line-by-line producers
    async function* tail() {
      for await (const line of process.stdin) yield line;
    }
    await (await pipe.send('live-logs', tail())).done;

    // Receiver: pipe Web ReadableStream straight to stdout
    const recv = await pipe.receive('live-logs');
    await recv.body.pipeTo(Writable.toWeb(process.stdout));
    ```
  


### Multi-Receiver Fan-Out

Send once, stream to multiple receivers simultaneously:


  
    ```bash
    # Sender: Stream to 3 receivers
    hoody pipe send demo ./presentation.webm -n 3

    # Receiver 1, 2, 3: Each runs this
    hoody pipe receive demo -n 3 -o presentation.webm
    ```
  
  
    ```bash
    # Sender: Stream to 3 receivers
    curl -T ./presentation.webm \
      "https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/demo?n=3"

    # Receiver 1, 2, 3: Each runs this (all get identical copies)
    curl "https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/demo?n=3" \
      -o presentation.webm
    ```
  
  
    ```typescript
    // Sender: Stream to 3 receivers
    const send = await pipe.send('demo', createReadStream('./presentation.webm'), { n: 3 });
    await send.done;

    // Each receiver
    const recv = await pipe.receive('demo', { n: 3 });
    await recv.body.pipeTo(Writable.toWeb(createWriteStream('./presentation.webm')));
    ```
  



The `n` parameter must match between sender and all receivers. The transfer doesn't begin until all `n` receivers and the sender are connected.


---

## API Endpoints Summary

All endpoints accessed relative to your Pipe service URL:
```
https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu
```

**Data Transfer**:
- `POST /api/v1/pipe/{path}` — Send data to a pipe path
- `PUT /api/v1/pipe/{path}` — Send data (alias for POST, natural for `curl -T`)
- `GET /api/v1/pipe/{path}` — Receive data from a pipe path

**Utilities**:
- `GET /api/v1/pipe/health` — Service health check (standardized 9-field response: `status`, `service`, `built`, `started`, `memory`, `fds`, `pid`, `ip`, `userAgent`). Build identity is in `built` (module mtime, ISO 8601); `userAgent` echoes the requesting client's `User-Agent` header.
- `GET /api/v1/pipe/help` — Usage instructions with curl examples
- `GET /api/v1/pipe/noscript` — JavaScript-free HTML upload form (noscript fallback for the index page; CSP-nonce-restricted styles)
- `GET /api/v1/pipe` — Main web UI for browser-based uploads (file or text, with a JS progress bar)

---

## Command-line Interface

The `hoody` CLI ships a `pipe` namespace that wraps every operation above with shell-friendly source/sink flags. Bytes can come from a file, stdin, a literal `--text` value, a TCP/Unix socket, or any spawned command — and likewise on the receiving side.

```bash
# Sources for `send`: file, "-" (stdin), --text, --from-tcp, --from-unix, --from-cmd
hoody pipe send <path> [source] [flags]

# Sinks for `receive`: file via -o, "-" (stdout, default), --to-tcp, --to-unix, --to-cmd
hoody pipe receive <path> [flags]
```

### Commands

| Command | Purpose |
|---------|---------|
| `pipe send <path> [source]` | Send bytes to a pipe path. Source = file / `-` / `--text` / `--from-tcp` / `--from-unix` / `--from-cmd`. Add `--put` for `curl -T` parity. |
| `pipe receive <path>` | Receive bytes. Sink = `-o file` / stdout / `--to-tcp` / `--to-unix` / `--to-cmd`. Honors `--download`, `--inline`, `--filename`. |
| `pipe progress <path>` | Subscribe to live progress via SSE — does **not** consume a receiver slot. `--json` emits raw events; `--until complete,failed` exits on terminal state. |
| `pipe url <path>` | Print the receiver URL for a path (no fetch). Append `--video`, `--progress`, `--download`, `--filename`, `-n N`. `--video` and `--progress` are mutually exclusive. |
| `pipe forward-tcp <send-path> <recv-path>` | Bidirectional TCP forwarder over two pipes. Pick exactly one of `--listen host:port` or `--connect host:port`; the peer must swap the two paths. |
| `pipe health` | Standard 9-field pipe-kit health JSON. |
| `pipe help-cheatsheet` | Server-rendered curl cheatsheet (text/plain). |

### Routing flags (all commands)

| Flag / env | Purpose |
|------------|---------|
| `-c <id>` / `--container <id>` / `HOODY_CONTAINER` | Container to address (resolves the kit URL through the Hoody API). `--container-id` and `--containerId` are accepted aliases. |
| `--pipe-url <url>` / `HOODY_PIPE_URL` | Direct pipe-server URL — bypasses container resolution. Useful for self-hosted pipe servers, tests, or off-network clients. |

### Examples

```bash
# Bridge a TCP port over a pipe (e.g. expose a localhost service to a peer)
# Side A (the side hosting the service)
hoody pipe forward-tcp ab ba --connect 127.0.0.1:5432 --container $CTR
# Side B (the side dialing in)
hoody pipe forward-tcp ba ab --listen 127.0.0.1:15432 --container $CTR
# Now `psql -h 127.0.0.1 -p 15432` on side B reaches the Postgres on side A.

# Stream stdout of a long-running command
hoody pipe send build-logs --from-cmd "make -j" --container $CTR

# Watch a transfer's progress as JSON without consuming a slot
hoody pipe progress big-upload --json --until complete,failed

# Build a shareable URL for an n=10 video stream — no request issued
hoody pipe url my-screen --video -n 10 --container $CTR
```


The CLI is a thin wrapper over the SDK's `PipeStream` helper — `--from-tcp` / `--from-unix` / file / stdin / `--text` all map to `PipeSource` shapes; `--from-cmd` / `--to-cmd` add convenient process spawning on top. See [SDK Streaming Helpers](#sdk-streaming-helpers-nodejs) below.

Both `--from-cmd` and `--to-cmd` spawn the command directly with **no shell**, so `"tail -f /var/log/app.log"` works but `"tail -f a | grep b"` does not — wrap shell features explicitly: `--from-cmd 'sh -c "tail -f a | grep b"'`.


---

## SDK Streaming Helpers (Node.js)

The auto-generated `containerClient.pipe.*` methods cover the basic JSON-shaped endpoints, but pipe is fundamentally a **streaming** primitive — the SDK ships a hand-written Node-side companion at `@hoody-ai/hoody-sdk`'s top level.

```typescript
import {
  PipeStream,                    // class — send / receive / subscribeProgress / forwardTcp
  coerceToReadableStream,        // anything-byte-source → Web ReadableStream
  parseStatusStream,             // [INFO]/[ERROR] line stream parser
  parseSseStream,                // SSE block parser (for /{path}?progress)
  PipeReceiveEmptyBodyError,     // distinguishes intentional empty bodies from network errors
  validatePipePath, encodePipePath,
} from '@hoody-ai/hoody-sdk';
```

### `PipeStream`

```typescript
// Either resolve from the Hoody control plane:
const pipe = PipeStream.fromClient(client, { id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });

// Or address a pipe server directly (self-hosted, tests, off-network):
const pipe = new PipeStream({ pipeBaseUrl: 'https://...-pipe.containers.hoody.icu' });

// send — accepts ANY PipeSource:
//   string | Buffer | Uint8Array | ReadableStream | NodeJS.ReadableStream
//   | AsyncIterable | URL (file:) | { tcp: { host, port } } | { unix: '/path' }
const send = await pipe.send('my-path', source, {
  method: 'POST',          // or 'PUT' (curl -T parity)
  n: 1,                    // 1..256 — receivers to wait for
  contentType: 'application/octet-stream',
  contentLength: stat.size, // optional — when known, enables receiver progress
  filename: 'report.pdf',  // forwarded as Content-Disposition
  headers: { 'X-Hoody-Pipe': 'kind=audit' },
  onStatus: (m) => console.log(m.level, m.message),
  signal: AbortSignal.timeout(60_000),
});
await send.done;           // resolves when sender's response body fully drains

// receive — gives you a Web ReadableStream
const recv = await pipe.receive('my-path', { n: 1, download: true });
recv.body.pipeTo(Writable.toWeb(createWriteStream('out.bin')));

// subscribeProgress — async-iterable SSE events (does NOT consume a receiver slot)
for await (const ev of pipe.subscribeProgress('my-path')) {
  if (ev.kind === 'progress') console.log(ev.bytesTransferred, ev.speed);
  if (ev.kind === 'done') break;
}
```

### `PipeStream.forwardTcp`

Bidirectional TCP forwarding over two unidirectional pipes — same primitive the CLI's `forward-tcp` is built from:

```typescript
const fwd = pipe.forwardTcp({
  sendPath: 'ab',                              // local → server
  recvPath: 'ba',                              // server → local
  connect: { host: '127.0.0.1', port: 5432 }, // OR listen: { port: 15432 }
  n: 1,
});
// fwd.address — Promise<{host, port}> when in listen mode
// fwd.done    — Promise<void> when fully drained
// fwd.close() — stop accepting / tear down active bridge
```


On Bun, TCP `allowHalfOpen: true` is not honored — peers that rely on FIN-as-end-of-record will see truncated reads. For protocols like that, frame your messages (length-prefix or fixed-size). This limitation is documented on the helper's JSDoc; the integration tests use a fixed-size echo protocol to demonstrate the workaround.


---

## Sender Status Messages

When you POST or PUT, the response body streams real-time status messages:

```
[INFO] Waiting for 1 receiver(s) to connect...
[INFO] A receiver connected.
[INFO] Streaming to 1 receiver(s)...
[INFO] Upload complete.
[INFO] Transfer complete.
```

If receivers were already waiting when the sender connects, the first line is replaced by `[INFO] N receiver(s) already connected.` and streaming begins immediately.

If a receiver disconnects mid-transfer:
```
[INFO] A receiver disconnected.
[INFO] All receivers disconnected before transfer completed.
```

`[ERROR] …` lines are emitted on failure modes — capacity exceeded, idle timeout, sender error, or wait timeout. The full authoritative vocabulary lives in `hoody-pipe`'s OpenAPI under `StatusMessage`.

---

## Receiver Options

Receivers can control download behavior with query parameters:

| Parameter | Effect |
|-----------|--------|
| `?download` | Force browser download (Content-Disposition: attachment) |
| `?download=false` | Suppress Content-Disposition entirely (always display inline) |
| `?filename=report.pdf` | Set a custom download filename (implies `?download`) |
| `?video` | Show an HTML video player for WebM/MP4/MPEG-TS streams (browsers only) |
| `?progress` | Watch transfer progress as SSE events or an HTML dashboard |

### Video Player

Append `?video` to the receiver URL and open it in a browser — hoody-pipe serves an embedded MSE video player that auto-detects the container/codec from the stream:

```bash
# Sender: Stream your screen as WebM
ffmpeg -f x11grab -i :0 -c:v libvpx -f webm - | \
  curl -T - https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/screen

# Receiver: Open in browser with video player
# https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/screen?video
```

Non-browser clients (VLC, mpv, ffplay) with `?video` get the raw stream automatically.

### Progress Spectating

Watch transfer progress without consuming a receiver slot:

```bash
# SSE stream (curl, EventSource)
curl -H "Accept: text/event-stream" \
  "https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/my-transfer?progress"

# HTML dashboard (open in browser)
# https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/my-transfer?progress
```

SSE events include `state` transitions (`idle` / `waiting` / `streaming` / `complete` / `failed`), `progress` updates (bytes, speed, ETA, active receivers), and a final `done` event with totals (bytes transferred, duration, average speed).

---

## Real-World Use Cases

### Share Your Screen Over HTTP

Stream your real device screen to anyone with a URL — no WebRTC, no signaling server, no app install:

```bash
# On your machine: Capture screen and pipe it
ffmpeg -f x11grab -i :0 -c:v libvpx-vp9 -f webm - | \
  curl -T - "https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/my-screen?n=10"

# Share this URL with up to 10 viewers:
# https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/my-screen?n=10&video
```

### File Transfer with Progress

Send a large file and let others watch the progress:

```bash
# Sender
curl -T ./dataset-50gb.tar.gz \
  https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/dataset

# Receiver
curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/dataset \
  -o dataset-50gb.tar.gz

# Spectator (doesn't consume a receiver slot)
# Open in browser: .../api/v1/pipe/dataset?progress
```

### Live Event Forwarding

Stream events from one container to another in real-time:

```bash
# Container A: Forward nginx access logs
tail -f /var/log/nginx/access.log | curl -T - \
  https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/nginx-logs

# Container B: Process the log stream
curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/nginx-logs | \
  grep "500" | tee errors.log
```

### Send a Directory

```bash
# Sender: Tar and stream a directory
tar czf - ./my-project | curl -T - \
  https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/project.tar.gz

# Receiver: Download and extract
curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/project.tar.gz | \
  tar xzf -
```

### End-to-End Encryption

Don't trust the server? Encrypt before piping:

```bash
# Sender: Encrypt and send
openssl enc -aes-256-cbc -pbkdf2 -pass pass:SECRET < secret.doc | \
  curl -T - https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/encrypted

# Receiver: Download and decrypt
curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/encrypted | \
  openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:SECRET > secret.doc
```

---

## Security

hoody-pipe takes security seriously:

- **Dangerous MIME types rewritten** — 21 script-executable types (HTML/XML/SVG plus the WHATWG-canonical JavaScript MIME family — `text/javascript`, `application/javascript`, `application/ecmascript`, `text/javascript1.0`–`1.5`, `text/jscript`, `text/livescript`, etc.) are rewritten to `text/plain` so a browser receiver can't execute them. Inline `Content-Disposition` is also force-coerced to `attachment` for any MIME outside an explicit safelist (`image/*`, `audio/*`, `video/*`, `text/plain`, …) — defense-in-depth if the dangerous-MIME table misses a niche format.
- **CRLF injection protection** — Forwarded headers (Content-Disposition, X-Piping, X-Hoody-Pipe) are sanitized against header injection.
- **CSP nonces** — All HTML pages (video player, progress dashboard, noscript upload) use Content-Security-Policy with nonces.
- **Path length limit** — Max 1024 characters to prevent memory inflation.
- **X-Content-Type-Options: nosniff** — On every response.
- **X-Robots-Tag: none** — Prevents indexing of pipe data.

---

## Limits

| Limit | Value |
|-------|-------|
| Max concurrent pending (unestablished) pipes | 1000 |
| Max concurrent active (streaming) transfers | 1000 |
| Max receivers per pipe | 256 |
| Max spectators per path | 50 |
| Max spectator groups | 500 |
| Unestablished pipe TTL | 5 minutes (sender or receiver waiting for counterpart; the lonely side is evicted) |
| Active-transfer idle timeout | 5 minutes (no bytes flowing → `[ERROR] Transfer aborted — idle timeout exceeded.`) |
| Spectator idle TTL | 30 minutes (progress-watcher auto-close on inactivity) |
| Max path length | 1024 characters |
| Max URL length | 4096 characters |

Exceeding limits returns HTTP 429 (Too Many Transfers) or 414 (Path Too Long).

---

## Custom Metadata

Forward arbitrary metadata from sender to receiver using custom headers:

```bash
# Sender: Include metadata
curl -T ./image.png \
  -H "X-Hoody-Pipe: source=camera-1;timestamp=2026-03-04T12:00:00Z" \
  -H "Content-Disposition: attachment; filename=\"snapshot.png\"" \
  https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/snapshot

# Receiver: Gets the metadata headers forwarded
curl -v https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/snapshot \
  -o snapshot.png
# < X-Hoody-Pipe: source=camera-1;timestamp=2026-03-04T12:00:00Z
# < Content-Disposition: attachment; filename="snapshot.png"
```

Both `X-Hoody-Pipe` and `X-Piping` headers are forwarded to receivers and exposed via CORS.

---

## Health & Monitoring


  
    ```bash
    # Standard 9-field health JSON
    hoody pipe health --container $CONTAINER
    ```
  
  
    ```bash
    # Check service health
    curl https://{projectId}-{containerId}-pipe-1.{server}.containers.hoody.icu/api/v1/pipe/health
    ```

    ```json
    {
      "status": "ok",
      "service": "hoody-pipe",
      "built": "2026-04-14T22:17:54Z",
      "started": "2026-04-22T09:12:03Z",
      "memory": { "rss": 44145050, "heap": 29884416 },
      "fds": 17,
      "pid": 1234,
      "ip": "172.17.0.2",
      "userAgent": "curl/8.4.0"
    }
    ```

    `userAgent` is the requesting client's `User-Agent` header, echoed back (useful for debugging client identity through the proxy). Server build identity lives in `built` (the module's mtime as an ISO 8601 string).
  
  
    ```typescript
    import { HoodyClient } from '@hoody-ai/hoody-sdk';

    const client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: process.env.HOODY_TOKEN });
    const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });

    // Health check — build identity is in `built` (module mtime, ISO 8601);
    // `userAgent` echoes whatever User-Agent the SDK's HTTP client sent.
    const health = await containerClient.pipe.health.check();
    console.log(health.data.status);  // "ok"
    console.log(health.data.built);   // "2026-04-14T22:17:54Z"
    ```
  


---

## Troubleshooting

### Transfer Never Starts

**Cause:** Sender and receiver have mismatched `n` values, or one party hasn't connected yet.

**Solution:** Ensure both sender and receiver use the same `?n=` value (or omit it for the default of 1). The connection blocks until the counterpart arrives — check that both parties are running.

### HTTP 429 — Too Many Transfers

**Cause:** Server has 1000 pending or 1000 active transfers.

**Solution:** Wait for existing transfers to complete or expire (5-minute TTL for unestablished pipes), then retry. Reduce the number of concurrent transfers.

### Receiver Gets `text/plain` Instead of Expected Content-Type

**Cause:** The sender's Content-Type is a dangerous MIME type (text/html, application/javascript, etc.) that was rewritten for security.

**Solution:** This is intentional XSS protection. If you need the original type, the receiver can re-set it based on the filename or use the data as-is.

### Path Already Has a Sender

**Cause:** Another sender is already connected to the same path.

**Solution:** Use a different path, or wait for the existing transfer to complete.

---

## What's Next


  
  
  
  


---

> **Data transfer is just a URL now.**
> **Send to a path. Receive from the same path. Data flows through.**
> **No servers to configure. No clients to install. No files to upload.**
> **Just HTTP.**