API

Upload a file, get a link back. No account, no signup, and no credential required — the API is as anonymous as the site.

Quick start

# upload one or more files
curl -F 'files[]=@photo.jpg' https://prnt.li/api/upload

# upload from something that can only stream a body
curl --data-binary @movie.mp4 'https://prnt.li/api/upload?filename=movie.mp4'

# what is this file?
curl https://prnt.li/api/file/<token>

# delete it
curl -X POST https://prnt.li/api/delete/<token>

Endpoints

RouteWhat it does
POST /api/uploadUpload files. Multipart, or a raw body.
GET /api/file/<token>Metadata: name, size, type, expiry, view and download counts.
POST /api/delete/<token>Delete permanently.
POST /api/unlock/<token>Exchange a password for an unlock token.
GET /api/configThe limits currently in force.
GET /api/openapi.jsonOpenAPI 3.1 — machine-readable, for generators.

Two request shapes, one route

Multipart is the primary one, because that is what every HTTP client library sends by default. A raw body is also accepted for clients that would otherwise have to hand-assemble a multipart document to move one file. They are told apart by Content-Type, and both go through the same size, quota and storage checks.

A raw upload carries one file, so the per-upload file count does not apply to it. The display name comes from the X-Filename header, or ?filename= as a fallback — a header, because query strings end up in access logs.

API keys

An API key is optional and buys exactly two things: the per-IP hourly ceiling rises tenfold, and downloads are exempt from the large-file throttle.

not configured here This server currently has no keys set, so every request takes the anonymous path.

What a key deliberately does not buy:

Send one as X-API-Key or Authorization: Bearer <key>.

Unlocking without a browser

/pw/ hands out a cookie. A script cannot use a cookie, so POST /api/unlock/<token> returns the same signed value in a header:

UNLOCK=$(curl -s -d 'password=hunter2' https://prnt.li/api/unlock/<token> \
         | python3 -c 'import json,sys; print(json.load(sys.stdin)["unlock"])')

curl -H "X-PRNT-Unlock: $UNLOCK" https://prnt.li/f/<token>/secret.pdf

The signature is scoped to that one file. That is load-bearing: the cookie version signs a bare 1 and relies on the cookie's name to scope it, and a header has no name — so an unscoped signature would unlock every password-protected file on the host at once.

Status codes

Errors are {"error": "..."} — a flat object, a single string, safe to show a user.

CodeMeaning
400Bad request, or every file in the batch was rejected.
401Wrong password.
404No such file.
413Larger than the per-file limit.
429Hourly limit reached, or too many password attempts.
503Too many uploads already in flight.
507Out of storage or disk space.

A batch can partially fail and still return 200. One oversized file does not cost you the other nine, so an errors array is present on success and must be checked even then. Only a request where nothing was stored returns 400.

Limits

Value
per file1024 MiB
files per upload10
per IP20/hour
expiry, clamped to365 days

Expiry is clamped rather than trusted: an anonymous client sending 9999-01-01 would otherwise have a way to disable it.

An inbox.li project, hosted in Norway.