The translation
The transfer.sh facts are from its README as read on 4 November 2026, the file.io facts from its own pages the same day.
| Step | transfer.sh | aisenseapi.com |
|---|---|---|
| Upload a file | curl --upload-file f https://transfer.sh/f | curl -F file=@f to /services/v1/storage |
| Upload text or JSON | The same, as a file | The raw body with Content-Type: text/plain or application/json, stored verbatim |
| The answer | The download URL as text | JSON: storage_id, storage_url, sha256_hash, bytes and the expiry |
| Limit the downloads | -H "Max-Downloads: 1" | /storage/max_downloads/1 in the path, 1 to 100; the answer adds downloads_left |
| Set the lifetime | -H "Max-Days: 1" | Fixed: 24 hours from upload, or the last allowed download, whichever comes first |
| Download | curl https://transfer.sh/... | curl -o f "$storage_url"; each download reports Downloads-Left |
| Delete early | An X-Url-Delete URL in the upload response | Not offered; choose the download count instead |
| Integrity | Not stated | sha256_hash of the stored bytes in the upload answer |
| Size | Not stated for the public host | 80 MB per address per day across all uploads; JSON bodies at most 256 KiB |
| Account or key | None | None |
| Status | Public host unreachable when read; the README says host your own | Hosted, HTTPS only |
| file.io, for comparison | Browser uploads only; its API is retired. Free files up to 4 GB, kept 3 days, up to 10 downloads per file per day, no account. | |
The upload and its answer:
curl -s -F file=@report.pdf https://aisenseapi.com/services/v1/storage/max_downloads/1{
"storage_id": "5e1c0a7e-9b3f-4c62-8a1d-2f7b6d4c9e10",
"storage_url": "https://aisenseapi.com/services/v1/storage/5e1c0a7e-9b3f-4c62-8a1d-2f7b6d4c9e10",
"sha256_hash": "700d8cea137c32c31e4865aaf5ae327931067ae454bc78777f8572dadf9c9809",
"bytes": 48213,
"expire_timestamp": 1793881430,
"expire_datetime": "2026-11-05T12:23:50+00:00",
"downloads_max": 1,
"downloads_left": 1
}What differs
- Time-boxed by design. transfer.sh let the uploader pick days. Here every object lives at most 24 hours; with
max_downloadsit can live shorter, never longer. This is a hand-off, not a place to keep files. - A link that works exactly once.
max_downloads/1makes a link whose first fetch is the only fetch; the object answers 410 afterwards. It is the right shape for passing a file to one recipient or one machine, with the SHA-256 to prove what arrived. - A daily budget rather than a size cap. An address may store 80 MB per day in total; past that an upload answers 429 until the counter resets at midnight in Norway. That fits logs, exports, images and documents, and does not fit video.
- Executables are refused. A body whose first bytes are a Windows, Linux or macOS executable, or a Java class, is refused whatever its name or declared type says.
- JSON answers. transfer.sh answered a URL as text. Here the upload answers JSON with the id, the URL, the hash, the size and the expiry, so a script checks the hash and keeps the URL without parsing text.
- No custom names. transfer.sh kept the file name in the link. Here the link is a UUID; keep the name on your side, or in the hand-off message.
A hand-off from a shell and from Python
Upload once, pass the URL on, and let the single download remove the object:
answer=$(curl -s -F file=@build.log https://aisenseapi.com/services/v1/storage/max_downloads/1)
url=$(printf '%s' "$answer" | jq -r .storage_url)
sha=$(printf '%s' "$answer" | jq -r .sha256_hash)
echo "Fetch once: $url (sha256 $sha)"
# the recipient
curl -s -D - -o build.log "$url" | grep -i downloads-left # Downloads-Left: 0
sha256sum build.logThe same in Python, with the hash check on the receiving side:
import hashlib, requests
BASE = "https://aisenseapi.com/services/v1/storage"
with open("build.log", "rb") as handle:
stored = requests.post(BASE + "/max_downloads/1", files={"file": handle}, timeout=60).json()
print(stored["storage_url"], stored["bytes"], stored["sha256_hash"])
got = requests.get(stored["storage_url"], timeout=60)
assert hashlib.sha256(got.content).hexdigest() == stored["sha256_hash"]
print("downloads left:", got.headers.get("Downloads-Left"))Text and JSON need no form: send the body with its content type and it is stored byte for byte.
curl -s -X POST https://aisenseapi.com/services/v1/storage \
-H "Content-Type: text/plain" \
--data-binary @notes.txtThe fields
| Field | Type | Meaning |
|---|---|---|
| storage_id | string | The UUID of the object; the only thing that reads it. |
| storage_url | string | The GET URL that returns the stored bytes. |
| sha256_hash | string | SHA-256 of the bytes as stored, so the recipient can check what arrived. |
| bytes | integer | The stored length. |
| expire_timestamp, expire_datetime | integer, string | When the object is removed, 24 hours after upload. |
| downloads_max, downloads_left | integer | Present when a download limit was set; each download also answers a Downloads-Left header. |
Errors
| Case | Status | What it means |
|---|---|---|
| Unknown id | 404 | No object has that id. |
| Expired, or the last allowed download has gone out | 410 | The bytes are removed; the id answers 410 for the rest of its 24 hours. |
| The day's storage budget or request quota used up | 429 | 80 MB stored per address per day, or 5,000 requests; wait for the reset. |
Every refusal is JSON with error and fix.
Privacy and limits
Whoever holds the storage_url can fetch the bytes until they expire or the downloads run out, so treat it as a capability link and send it only to the intended recipient. Do not store secrets, credentials or personal data here; the One-time Secret endpoint exists for a secret that must be read once. See our privacy policy for log retention.
Every path shares the service-wide ceiling of 5,000 requests per IP address per day, and uploads share the 80 MB per address per day. transfer.sh's README documents Max-Downloads, Max-Days and a delete URL and states no size or retention for the public host; file.io states 4 GB, 3 days and 10 downloads per file per day for a free upload in the browser. Those are their statements as of November 2026 and may change.