# send a file: password auto-generated, prints link + password $ ttl send secret.pdf No password provided. Generate one? [Y/n]: Generated password: aB3kL9mXq7Rt ·✧★◉ Thank goodness, secret.pdf is in orbit (1.2 MB, expires 2026-10-08 12:00) IMPORTANT! Save your password — required to download and decrypt the file. Password: aB3kL9mXq7Rt Manage key: k7Qm2vXpL9aR4tB8cD1eF6gH3jK5mN0oP2qS4uV7wYz (ttl status / ttl delete -k) https://ttl.space/aBcDeFgHiJ # download: prompts for password interactively $ ttl get https://ttl.space/aBcDeFgHiJ Enter password: ******** Password verified ◉★✧· Phew, secret.pdf landed safe and sound (1.2 MB)
The CLI handles encryption, upload, download, and decryption. The server never sees your password or plaintext data.
$ brew install tweenietomatoes/ttl/ttl
$ scoop bucket add ttl https://github.com/tweenietomatoes/scoop-ttl $ scoop install ttl
# download for your platform from releases $ curl -Lo ttl.tar.gz \ https://github.com/tweenietomatoes/ttl/releases/download/v1.6.0/ttl_1.6.0_linux_amd64.tar.gz $ tar xzf ttl.tar.gz && sudo mv ttl /usr/local/bin/
$ go install github.com/tweenietomatoes/ttl/cmd/ttl@latest
$ git clone https://github.com/tweenietomatoes/ttl $ cd ttl && go build -o ttl ./cmd/ttl/ $ sudo mv ttl /usr/local/bin/
$ ttl version ttl 1.6.0
# auto-generate password, default 7d TTL $ ttl send report.xlsx
When no password is provided in a terminal, the CLI asks:
No password provided. Generate one? [Y/n]:
Press Enter or type y to auto-generate.
$ ttl send -p mySecretPass -t 1h document.pdf
# used up the moment the first download starts; after a break only that download can resume it $ ttl send -b photo.jpg
$ ttl send -t 5m temp-credentials.txt # expires in 5 minutes $ ttl send -t 30m meeting-link.txt # expires in 30 minutes $ ttl send -t 6h backup.tar.gz # expires in 6 hours $ ttl send -t 1d daily-report.pdf # expires in 1 day $ ttl send -t 3d project-archive.zip # expires in 3 days $ ttl send -t 5d design-assets.zip # expires in 5 days $ ttl send report.xlsx # expires in 7 days (default)
A file of up to 16 MiB goes in one request; a larger one in resumable 16 MiB parts, two at a time. Each request is checked with a SHA-256 digest, and one that fails on the way is sent again; parts already sent stay. Ctrl-C cancels cleanly.
$ ttl get https://ttl.space/aBcDeFgHiJ Enter password: ******** Password verified ◉★✧· Phew, document.pdf landed safe and sound (4.2 MB)
The password is not echoed to the terminal. The original filename is preserved; if that name already exists in the output directory, the file is saved as name (1).ext instead of overwriting it.
# you can use just the 10-character token instead of the full URL $ ttl get aBcDeFgHiJ
$ ttl get -o ~/Downloads aBcDeFgHiJ
By default, files are saved to the current directory. Use -o / --output to specify a different directory.
$ ttl get -p mySecretPass https://ttl.space/aBcDeFgHiJ
A download that breaks resumes from the last byte received; every 64 KiB chunk is authenticated on its own, so the result is identical to an unbroken transfer. A one-time (burn after reading) file is used up when its download starts; after a break, only that same download picks it up again, for a few minutes.
You can also download and decrypt files in your browser by visiting the link directly. The web interface uses the same file format and algorithms (Argon2id, XChaCha20-Poly1305) and prompts for your password. No software installation needed.
Every upload comes with a management key, printed once by ttl send (manage_key in --json). The server keeps only its hash. On any plan it lets you follow the upload or end it early; anyone holding the key can delete the upload, so keep it like the password.
# state, downloads and expiry of your own upload $ ttl status aBcDeFgHiJ -k k7Qm2vXpL9aR4tB8cD1eF6gH3jK5mN0oP2qS4uV7wYz Token: aBcDeFgHiJ State: active Size: 4.2 MB Created: 2026-10-01 12:00 Expires: 2026-10-08 12:00 Downloads: 1 (last 2026-10-02 09:12) # delete it early $ ttl delete aBcDeFgHiJ -k k7Qm2vXpL9aR4tB8cD1eF6gH3jK5mN0oP2qS4uV7wYz Deleted: aBcDeFgHiJ
The state is one of active, expired, burned or deleted.
The password is the encryption key seed. It never leaves your device and is never sent to the server. There are six ways to provide it:
$ ttl send notes.txt No password provided. Generate one? [Y/n]: y Generated password: aB3kL9mXq7Rt
Generates a cryptographically random 12-character password from [0-9A-Za-z]. Only available during send (interactively, or automatically in --json mode).
$ ttl send notes.txt No password provided. Generate one? [Y/n]: n Enter password: ******** Confirm password: ******** $ ttl get https://ttl.space/aBcDeFgHiJ Enter password: ********
Password is not echoed. During send, you choose whether to auto-generate; if you type your own, you confirm it. During get, you are prompted directly.
-p / --password$ ttl send -p mySecretPass file.zip $ ttl get --password mySecretPass https://ttl.space/aBcDeFgHiJ
-p / --password is visible in ps aux output and may be saved in shell history (~/.bash_history). Avoid in shared environments and scripts. Use --password-stdin or --password-file instead.--password-stdin# pipe from echo $ echo "mySecretPass" | ttl send --password-stdin backup.tar.gz # pipe from password manager $ pass show ttl/key | ttl send --password-stdin data.zip # pipe from environment variable $ printenv MY_SECRET | ttl get --password-stdin https://ttl.space/aBcDeFgHiJ # heredoc $ ttl send --password-stdin report.pdf <<<"mySecretPass"
Reads the first line from stdin. Useful in scripts, CI/CD pipelines, and automation. When stdin is not a terminal and no password source is provided, the CLI exits with an error (unless --json is used, which auto-generates a password).
--password-file$ ttl send --password-file /run/secrets/key report.xlsx $ ttl get --password-file ~/.ttl-key https://ttl.space/aBcDeFgHiJ
Reads the first line of the file (trailing newline stripped). Compatible with Docker secrets (/run/secrets/), Vault agent files, and similar patterns.
ttl.passwordIf no explicit password is given, the CLI looks for a ttl.password file next to the binary, then ~/.ttl/password. The first line is used as the password.
-p / --password, --password-stdin, and --password-file is an error. The auto-detected ttl.password file is only checked when none of these are given. Minimum password length is 8 characters.| Flag | Description | Default |
|---|---|---|
-p, --password P | Encryption password | interactive |
--password-stdin | Read password from stdin | |
--password-file F | Read password from file | |
-t, --ttl DUR | Time to live | 7d |
-b, --burn | Burn after reading (single download) | off |
-u, --uploader-only | Only your own API key can download (Orbit; alias --private) | off |
--timeout DUR | Transfer timeout | none |
--server URL | Server URL | https://ttl.space |
-h3, --http3 | Try HTTP/3 (QUIC), fall back to TCP | off |
--json | Output JSON to stdout (for scripts and AI agents) | off |
| Flag | Description | Default |
|---|---|---|
-p, --password P | Decryption password | interactive |
--password-stdin | Read password from stdin | |
--password-file F | Read password from file | |
-o, --output DIR | Output directory | current directory |
--timeout DUR | Transfer timeout | none |
--server URL | Server for a bare token | https://ttl.space |
-h3, --http3 | Try HTTP/3 (QUIC), fall back to TCP | off |
--json | Output JSON to stdout (for scripts and AI agents) | off |
Free: 5m 10m 15m 30m 1h 2h 3h 6h 12h 24h 1d 2d 3d 4d 5d 6d 7d
Orbit adds: 14d 15d 28d 30d permanent
Default: none. A stalled transfer resumes by itself, and gives up if it cannot go on. Set a limit with --timeout 30m or --timeout 2h.
#!/bin/bash tar czf /tmp/backup.tar.gz /data echo "$BACKUP_KEY" | ttl send --password-stdin -t 6h /tmp/backup.tar.gz rm /tmp/backup.tar.gz
$ ttl send -p mypass123 file.pdf | pbcopy
$ ttl send -p mypass123 file.pdf | xclip -sel clip
$ echo "$DB_PASSWORD" | ttl send --password-stdin -b -t 5m credentials.env # link works exactly once, expires in 5 minutes
# send: password auto-generated in JSON mode $ ttl --json send report.pdf {"ok":true,"link":"https://ttl.space/xK9mQ2vLpA","token":"xK9mQ2vLpA","filename":"report.pdf","size":2097152,"ttl":"7d","expires_in":604800,"expires_at":1760011200,"burn":false,"uploader_only":false,"is_permanent":false,"manage_key":"k7Qm2vXpL9aR4tB8cD1eF6gH3jK5mN0oP2qS4uV7wYz","password":"aB3kL9mXq7Rt"} # get $ ttl --json get -p aB3kL9mXq7Rt xK9mQ2vLpA {"ok":true,"token":"xK9mQ2vLpA","filename":"report.pdf","size":2097152,"saved_to":"/home/user/report.pdf"} # status with the manage key $ ttl --json status xK9mQ2vLpA -k k7Qm2vXpL9aR4tB8cD1eF6gH3jK5mN0oP2qS4uV7wYz {"ok":true,"token":"xK9mQ2vLpA","status":{"state":"active","size_bytes":2097152,"created_at":1759406400,"expires_at":1760011200,"permanent":false,"burn":false,"note":false,"uploader_only":false,"downloads":1,"last_download_at":1759492800,"part_reads":0,"last_part_at":0}} # errors return ok:false {"ok":false,"error":"Link not found. The file may have expired, been downloaded already (burn after reading), or be private (uploader-only)"}
In --json mode, all output is structured JSON on stdout. If no password is provided during send, one is auto-generated and included in the response. Exit code is 0 on success, 1 on error.
Orbit unlocks larger files (10 GB), longer retention (30 days, or permanent), uploader-only files (only your API key can download them), and file management (list & delete). Full comparison: Features.
The API key is auto-detected from TTL_API_KEY env, ttl.key next to the binary, or ~/.ttl/key. Use ttl activate to save the key, or write it to any of these locations manually.
# activate your key: prompted without echo, so it stays out of shell history $ ttl activate Enter Orbit API key: ******** Orbit plan activated. Key saved to /usr/local/bin/ttl.key # non-interactive: ttl activate --key-stdin | ttl activate --key-file F # check plan and usage $ ttl plan Plan: orbit Max file size: 10.0 GB Max TTL: 30 days (or permanent) Uploads per day: 1000000 Storage quota: 500.0 GB Usage: Uploads today: 3 Active storage: 1.2 GB / 500.0 GB # send with extended TTL $ ttl send -t 30d large-backup.tar.gz # permanent file (Orbit only): no expiry, kept until you delete it or your subscription ends $ ttl send -t permanent archive.tar.gz # send a private file: only your API key can download it (Orbit only) $ ttl send -u payroll.xlsx # list recent uploads $ ttl list xK9mQ2vLpA 4.2 MB 2026-03-16 10:30 → 2026-04-15 10:30 [active] https://ttl.space/xK9mQ2vLpA rT7bNw3KpF 912.0 KB 2026-03-15 14:20 → 2026-03-17 14:20 [active (burn)] https://ttl.space/rT7bNw3KpF # delete a file early (ttl list walks every page; -n 5 stops after five files) $ ttl delete xK9mQ2vLpA Deleted: xK9mQ2vLpA # or with the upload's management key, on any plan: ttl delete xK9mQ2vLpA -k KEY # remove stored key $ ttl deactivate Key file removed: /usr/local/bin/ttl.key
ttl send / ttl get or --json mode for scripting. The endpoints below are for Orbit file management.$ curl https://ttl.space/v1/files -H "X-API-Key: ttl_orbit_..." {"files":[{"token":"xK9mQ2vLpA","link":"https://ttl.space/xK9mQ2vLpA","size_bytes":1048576,"created_at":1711800600,"expires_at":1714392600,"burn":false,"expired":false,"uploader_only":false,"is_permanent":false,"is_note":false,"perm_grace_until":0}],"has_more":false} # next page: only when has_more is true; pass next_cursor from the previous response $ curl "https://ttl.space/v1/files?cursor=1711800600123456" -H "X-API-Key: ttl_orbit_..."
Returns files uploaded with this key, newest first, 20 per page. When has_more is true the response also includes next_cursor; pass it as ?cursor= to fetch the next page. Requires Orbit plan.
$ curl -X DELETE https://ttl.space/v1/files/xK9mQ2vLpA -H "X-API-Key: ttl_orbit_..."
Returns 204 No Content on success. Requires Orbit plan. The file must be owned by the given key.
| Status | Meaning |
|---|---|
401 | Invalid or expired API key |
403 | Feature requires Orbit plan |
404 | File not found or not owned by this key |
429 | Rate limit exceeded |
500 | Internal server error, retry later |
502 | Storage temporarily unavailable, retry later |
| Constraint | Free | Orbit |
|---|---|---|
| Max file size | 2 GB | 10 GB |
| Max TTL | 7 days | 30 days or permanent |
| Uploads per day | 10 | Unlimited |
| Upload volume per day (per IP) | 10 GB | Unlimited |
| Concurrent uploads | 1 per IP | 1 per IP and per API key |
| Storage quota | ✗ | 500 GB |
| File listing | ✗ | ✓ |
| Early delete | ✓ | ✓ |
| Uploader-only files | ✗ | ✓ |
| Min password length | 8 characters | |
| Requests per IP | 30 per 10 seconds | |
| Connections per IP | 10 concurrent | |
Files are encrypted on your device before upload. The server stores only ciphertext and cannot decrypt your files.
| Component | Algorithm / Value |
|---|---|
| Key derivation | Argon2id (time=3, memory=64 MiB, parallelism=1) |
| Cipher | XChaCha20-Poly1305 AEAD |
Any modification to the ciphertext causes decryption to fail. The server verifies that the downloader knows the correct password before releasing the ciphertext, using a one-way token derived from the encryption key.