Usage Guide

English

Quick start

# 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.

Install

Homebrew (macOS)

$ brew install tweenietomatoes/ttl/ttl

Scoop (Windows)

$ scoop bucket add ttl https://github.com/tweenietomatoes/scoop-ttl
$ scoop install ttl

Pre-built binary

# 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

$ go install github.com/tweenietomatoes/ttl/cmd/ttl@latest

From source

$ git clone https://github.com/tweenietomatoes/ttl
$ cd ttl && go build -o ttl ./cmd/ttl/
$ sudo mv ttl /usr/local/bin/

Verify

$ ttl version
ttl 1.6.0

Sending files

Basic send

# 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.

Custom password

$ ttl send -p mySecretPass -t 1h document.pdf

Burn after reading

# used up the moment the first download starts; after a break only that download can resume it
$ ttl send -b photo.jpg

Custom TTL

$ 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)

Large files

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.

Downloading files

Interactive (terminal)

$ 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.

With token only

# you can use just the 10-character token instead of the full URL
$ ttl get aBcDeFgHiJ

Output directory

$ ttl get -o ~/Downloads aBcDeFgHiJ

By default, files are saved to the current directory. Use -o / --output to specify a different directory.

With password flag

$ ttl get -p mySecretPass https://ttl.space/aBcDeFgHiJ

Interrupted downloads

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.

Browser

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.

Manage key

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.

Password handling

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:

1. Auto-generate (send only)

$ 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).

2. Interactive prompt

$ 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.

3. Flag: -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.

4. Stdin: --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).

5. File: --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.

6. Auto-detected file: ttl.password

If 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.

Only one explicit password source can be used at a time. Combining -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.

Options reference

Send flags

FlagDescriptionDefault
-p, --password PEncryption passwordinteractive
--password-stdinRead password from stdin
--password-file FRead password from file
-t, --ttl DURTime to live7d
-b, --burnBurn after reading (single download)off
-u, --uploader-onlyOnly your own API key can download (Orbit; alias --private)off
--timeout DURTransfer timeoutnone
--server URLServer URLhttps://ttl.space
-h3, --http3Try HTTP/3 (QUIC), fall back to TCPoff
--jsonOutput JSON to stdout (for scripts and AI agents)off

Get flags

FlagDescriptionDefault
-p, --password PDecryption passwordinteractive
--password-stdinRead password from stdin
--password-file FRead password from file
-o, --output DIROutput directorycurrent directory
--timeout DURTransfer timeoutnone
--server URLServer for a bare tokenhttps://ttl.space
-h3, --http3Try HTTP/3 (QUIC), fall back to TCPoff
--jsonOutput JSON to stdout (for scripts and AI agents)off

TTL values

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

Timeout

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.

Examples

Script: backup and upload

#!/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

Pipe link to clipboard (macOS)

$ ttl send -p mypass123 file.pdf | pbcopy

Pipe link to clipboard (Linux)

$ ttl send -p mypass123 file.pdf | xclip -sel clip

Burn: one-time secret

$ echo "$DB_PASSWORD" | ttl send --password-stdin -b -t 5m credentials.env
# link works exactly once, expires in 5 minutes

JSON mode (scripts & AI agents)

# 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 plan

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

HTTP API

Upload and download require the CLI's encryption pipeline (Argon2id + XChaCha20-Poly1305 + chunked AEAD). Use ttl send / ttl get or --json mode for scripting. The endpoints below are for Orbit file management.

List files (Orbit)

$ 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.

Delete file (Orbit)

$ 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.

Error responses

StatusMeaning
401Invalid or expired API key
403Feature requires Orbit plan
404File not found or not owned by this key
429Rate limit exceeded
500Internal server error, retry later
502Storage temporarily unavailable, retry later

Limits

ConstraintFreeOrbit
Max file size2 GB10 GB
Max TTL7 days30 days or permanent
Uploads per day10Unlimited
Upload volume per day (per IP)10 GBUnlimited
Concurrent uploads1 per IP1 per IP and per API key
Storage quota✗500 GB
File listing✗✓
Early delete✓✓
Uploader-only files✗✓
Min password length8 characters
Requests per IP30 per 10 seconds
Connections per IP10 concurrent

Encryption

Files are encrypted on your device before upload. The server stores only ciphertext and cannot decrypt your files.

ComponentAlgorithm / Value
Key derivationArgon2id (time=3, memory=64 MiB, parallelism=1)
CipherXChaCha20-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.