Paste
NULL paste at paste.nullnet.sh is a zero-knowledge pastebin for text and files. Your client encrypts the paste before upload. The server stores ciphertext it cannot read, and the key travels only in the link you share. The API base is https://paste.nullnet.sh/api.
The API calls a stored paste a drop. The paste service has no accounts of its own; anyone can create a drop. A NULL Premium pass raises the limits and unlocks extra features.
Zero-knowledge model
"Zero-knowledge" here means the server learns nothing about the content. It does not mean zero-knowledge proofs.
- The client builds a plaintext (a small JSON manifest plus the text or file bytes), generates a fresh random 32-byte key, and encrypts the plaintext with AES-256-GCM into an envelope.
- The client uploads the envelope. The server checks only its version byte and length, stores it, and returns a drop id and a manage token.
- The client builds the share link with the key in the URL fragment (the part after
#). Browsers never send the fragment to a server. - The recipient's client fetches the envelope by id and decrypts it locally with the key from the fragment.
The server never receives the key, the plaintext, filenames, media types or the number of files. It does see the drop id, the envelope length (rounded up by padding), the expiry and view options, request timing and the connecting IP address. For a password-protected drop it also sees that a password is used and the Argon2id parameters.
Anyone holding the full link can decrypt the drop. Treat the link as the secret. The byte-level format is on the envelope format page.
The web app is JavaScript served by the same site, so you trust the server to serve honest code. A strict Content Security Policy and self-hosted assets reduce that risk but do not remove it. Your own client, built from the envelope format, avoids it.
Links
Share link https://paste.nullnet.sh/p/<id>#k=<key>
https://paste.nullnet.sh/p/<id>#k=<key>&p=1 password-protected
Manage link https://paste.nullnet.sh/m/<id>#t=<manage_token>
<id>is 22 base64url characters (128 random bits).<key>is the 32-byte key as unpadded base64url, always 43 characters.p=1tells the reader to ask for a password before it opens the drop. It is only a hint; the envelope itself says whether a password is needed.- The manage link carries the 43-character manage token, which deletes or extends the drop. The web app may also append
&k=<key>to the manage link so you can copy the share link again later.
Never put the key, the password or the manage token into a log or an error report, and never send the key to the API. The password is never part of any link.
Drop options
Each drop has an expiry and, optionally, a view limit. The expiry is chosen at creation from a fixed list of presets.
- TTL presets. Free: 10 minutes, 1 hour, 24 hours (
600,3600,86400seconds). Premium adds 7 days and 30 days (604800,2592000). Reading a drop never extends it. - Burn after reading.
burn=trueallows exactly one open. The drop is deleted as soon as the server reads it for that open. - View limit.
views=nallowsnopens (free: up to 10, premium: up to 100), then the drop is deleted. Withoutviewsorburn, a drop can be opened any number of times until it expires. - Password. The content key is derived from both the link key and a password, so neither works alone. The password never leaves the client. See version 2.
- Extend. With the manage token you can set a new expiry from the presets, up to 24 hours after creation for anonymous drops and up to 30 days for premium drops. See extend.
A view is consumed when the server reads the drop, before delivery is confirmed. If the connection drops during an open, a burn drop can be lost. A drop that is gone, expired, consumed or never existed always returns the same 404.
Retention
Drops are kept in memory only, not on durable storage. If the service restarts its store, every drop is lost, whatever its expiry. The expiry you choose is a maximum, not a promise of durability. Do not use paste as the only copy of anything.
Large premium envelopes (over 1 MiB + 16 KiB) are written to the server's disk instead. They are not backed up and are deleted when the drop expires, is consumed or is deleted. Deletion removes the file from the service; it is not a forensic erase of the disk. The content is ciphertext either way, but for secrets prefer burn after reading.
Free limits
Ask the server what you may do instead of hard-coding limits. GET /api/limits returns the caller's current limits and needs no authentication. This is the live answer for an anonymous caller:
Request
curl https://paste.nullnet.sh/api/limits
Response
{
"tier": "free",
"max_envelope_bytes": 1064960,
"ttl_presets_secs": [600, 3600, 86400],
"max_views": 10,
"max_files": 1,
"uploads_per_min": 10,
"reads_per_min": 60,
"features": [],
"quota": null,
"pass_expires_at": null
}
- Size. The envelope may be up to 1 MiB + 16 KiB (1,064,960 bytes), so the plaintext may be up to 1 MiB.
- Files. One entry per drop. Bundles of several files are a premium feature.
- Rate limits per client IP: 10 uploads per minute; 60 reads per minute (meta, open, delete, extend and limits combined); 5 abuse reports per minute. Over a limit you get
429 rate_limitedwith aRetry-Afterheader in seconds. - Upload time. The body must arrive within 15 seconds.
Field meanings are in the limits reference. Premium values are on the Premium page.
Errors
Errors are JSON with a stable code and a human-readable message. Match on code; the message may change.
Error
{ "error": { "code": "not_found", "message": "drop not found" } }
| Status | code | When |
|---|---|---|
| 400 | invalid_query | The query string does not parse (missing ttl, not a number). |
| 400 | invalid_options | ttl is not one of your presets, views is out of range, burn with views other than 1, or an extension past the maximum. |
| 400 | invalid_envelope | Unknown version byte, shorter than the version's minimum (29 bytes, or 87 for version 2), or a version 2 header with an unknown KDF or Argon2id parameters out of bounds. |
| 400 | invalid_json | A report body that is not JSON, not application/json, over 8 KiB, missing id or reason, or with unknown members. |
| 400 | invalid_report | A report with a malformed id, an unknown reason, or a bad note. |
| 400 | body_read | The client stopped sending the body. |
| 400 | invalid_header | If-Match is not one quoted version, or X-Zkbin-Alert-To is not a plain mailbox on an allowed domain. |
| 401 | missing_token | Delete or extend without a well-formed Authorization: Bearer manage token. |
| 403 | wrong_token | Delete or extend with a token that belongs to another drop. |
| 403 | tier_required | A premium feature (/api/vault, X-Zkbin-Alert-To) without a pass that enables it. |
| 404 | not_found | Missing, expired, consumed, or not a valid id. These cases are indistinguishable on purpose. |
| 408 | body_timeout | The upload did not arrive within 15 seconds (10 minutes for premium uploads over 1 MiB + 16 KiB). |
| 412 | vault_conflict | PUT /api/vault whose If-Match is not the stored version. |
| 413 | too_large | The body is larger than your max_envelope_bytes (or the vault cap). |
| 429 | quota_exceeded | A premium upload over the account's live drops or live storage. No Retry-After; it frees up as drops end. |
| 429 | rate_limited | Too many requests. Retry-After gives the seconds to wait. |
| 503 | storage_full | The service has no room for new drops. Existing drops are kept. |
| 503 | reports_full | Too many drops already have open reports. Write to support@nullnet.sh. |
| 503 | unavailable | Storage is unreachable or too slow. Retry later. |
| 500 | internal | A server bug. No detail is returned. |
A request that runs past the server's time budget is answered with 503.
Every response carries Cache-Control: no-store, X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer.
Health
| Request | Response |
|---|---|
GET https://paste.nullnet.sh/healthz | 200 with body ok while the service runs. |
GET https://paste.nullnet.sh/readyz | 200 with body ready when storage answers, 503 otherwise. |
Abuse
Anyone holding a link can report a drop with POST /api/reports, or by mail to support@nullnet.sh. Never include the #k= part of the link in a report.