WispFile for developers
The WispFile SDK adds browser-to-browser file transfer to any web app: your page shares files with a link, and they travel straight to whoever opens it, end-to-end encrypted, never stored. It is the same engine wispfile.com runs on, in one JavaScript module with no dependencies to install.
Key facts
- One module: https://wispfile.com/sdk/v1/wispfile.js
- Files are encrypted in the browser (AES-GCM, 256-bit key in the link) and never reach our servers.
- No account needed to start; an API key attributes transfers to your plan.
- The sending page must stay open while receivers download.
Quickstart
Paste this into any page. No build step, no key.
<input type="file" id="files" multiple>
<script type="module">
import { WispFile } from 'https://wispfile.com/sdk/v1/wispfile.js';
const wispfile = new WispFile();
document.querySelector('#files').addEventListener('change', async (event) => {
const share = await wispfile.send(event.target.files, {
onEvent: (e) => console.log(e.type, e),
});
console.log('Share this link:', share.link);
// Keep the page open: the files are served from here.
});
</script>The link opens on wispfile.com, where anyone can download without installing anything. Set appUrl to your own page if you receive links yourself.
Receiving in your page
const offer = await wispfile.open(link);
console.log(offer.files); // [{ name, size }]
const { files } = await offer.download({
onProgress: (received, total) => console.log(received / total),
});
// files: File[] (collected in memory by default)By default each file is collected in memory and returned as a File, which suits files up to a few hundred megabytes. For larger ones, give download an openSink that writes to disk as bytes arrive:
// Large downloads: write each file to disk as it arrives instead.
const dir = await window.showDirectoryPicker({ mode: 'readwrite' });
await offer.download({
openSink: async (file) => {
const handle = await dir.getFileHandle(file.name, { create: true });
const writable = await handle.createWritable();
return {
write: (bytes) => writable.write(bytes),
close: () => writable.close(),
abort: () => writable.abort(),
};
},
});Every chunk is checked against a SHA-256 fingerprint before it reaches your sink, and a dropped connection resumes where it stopped.
Reference
| Call | What it does |
|---|---|
new WispFile(options) | apiUrl, signalUrl, appUrl, apiKey, createTransfer, fetch: all optional |
send(files, options) | Shares File objects (or { file, path } for folders). Options: title, message, expires, password, onEvent. Resolves to { link, linkId, stop() } |
share.stop() | Ends the link for good: receivers are disconnected and it cannot be opened again |
open(link) | Reads what a link offers: files, totalBytes, title, message, requiresPassword |
offer.download(options) | Receives everything. Options: password, openSink, onProgress, signal. Resolves to { files } |
onEvent | receiver-joined, receiver-connected, progress, receiver-failed, receiver-left, error |
WispFileError | Thrown for API refusals; .code is the reason (for example rate-limited, forbidden) |
API keys
Without a key, transfers are anonymous and on the free terms (direct connections only). With a key, create one on your account page, transfers count as yours: they appear in your history, use your plan (including its relay for networks that block direct connections), and carry your branding.
Keys are secret by default. A key is refused when it is sent from a browser page, because anyone who opens a page can read its code. There are two ways to use one with a browser app:
- Mint transfers on your server (recommended). The key stays on your server; the SDK asks your server to create the transfer and serves the files itself.
- List allowed origins on the key, for example an internal tool at
https://tools.example.com. The key then works from pages on those origins only, and you accept that visitors to them can read it.
// Your server (Node): the only place the secret key lives.
app.post('/wispfile/transfers', async (req, res) => {
const answer = await fetch('https://api.wispfile.com/api/transfers', {
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${process.env.WISPFILE_API_KEY}`,
},
body: JSON.stringify(req.body), // the file list: names and sizes only
});
res.status(answer.status).json(await answer.json());
});
// Your page: the SDK asks your server instead of calling the API itself.
const wispfile = new WispFile({
createTransfer: (request) =>
fetch('/wispfile/transfers', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
}).then((response) => response.json()),
});A key can create and list transfers, nothing else: it cannot change your account, your billing, your team or other keys. Revoke a key on your account page and it stops working at once.
REST API
The SDK is a thin layer over these endpoints at https://api.wispfile.com. Request bodies are JSON and capped at 64 KB; file contents are never sent to the API.
| Endpoint | Purpose | Auth |
|---|---|---|
POST /api/transfers | Create a transfer from a file list (up to 1,000 per request); returns linkId and hostToken | Optional key |
POST /api/transfers/:linkId/files | Append more files (up to 10,000 in all) | hostToken |
GET /api/transfers/:linkId | What a link offers | None |
GET /api/turn-credentials?linkId=… | ICE servers for the link: STUN, and TURN on plans with a relay | None |
POST /api/transfers/:linkId/complete | Report a finished download (bytes and route only) | None |
POST /api/transfers/:linkId/close | End a link for good | hostToken |
GET /api/me/transfers | Your transfer history | Key or session |
Signaling is a WebSocket at wss://api.wispfile.com/ws; the SDK handles it. Errors come back as { "error": "…", "message": "…" }.
Limits
- 60 requests a minute per address without a key; 600 a minute per key.
- No limit on file size. Transfers are verified end to end up to 5 GB so far.
- Up to 10,000 files per transfer.
- The relay (TURN) follows the plan of the key’s owner; without a key, links are direct only.
Security model
- The SDK creates the encryption key in the browser and puts it only in the link’s fragment, after the
#. Browsers never send the fragment to a server; the API and signaling service never see it. - The API sees file names and sizes, and the title and message if you set them. Do not put secrets in them.
- The SDK never sends cookies to the API, so a visitor’s WispFile session can never be used by your page.
- Browser support: verified in Chromium-based browsers (Chrome, Edge); Firefox and Safari are in testing.
Versioning
/sdk/v1/ keeps a stable API; breaking changes will ship under /sdk/v2/. Questions or an integration to discuss: hi@mohiemen.com.
Send it directly now
Try the transfer yourself before you build on it: no account, nothing stored.
Send files with WispFile