◈ NullPaste api
Paste from curl
No account, no API key. Send raw text, get a link, a raw URL, and a one-time delete token back. Pastes created over the API get the same expiry, burn, and password options as the web editor — and the same 1 MB limit and per-IP rate limits.
Create a paste
curl --data-binary @file.txt \
-H "Content-Type: text/plain" \
https://nullpaste.org/api/v1/pastesThe request body is the paste text itself (Content-Type: text/plain — JSON bodies are rejected). A successful create returns 201:
{
"id": "AbC123xYz012",
"url": "https://nullpaste.org/AbC123xYz012",
"raw_url": "https://nullpaste.org/raw/AbC123xYz012",
"delete_token": "9f2kQ7wLpX4mR8sT2v",
"expires_at": "2026-08-31T06:00:00.000Z",
"burn": false,
"encrypted": false
}Save delete_token — it is shown only in this response, stored only as a hash, and is the only way to delete the paste before it expires.
| Header | Values | Notes |
|---|---|---|
| NP-Expiry | 10m | 1h | 1d | 1w | never | Default: 1d |
| NP-Language | auto | <language id> | Default: auto-detect |
| NP-Burn | 0 | 1 | Burn after first confirmed read |
| NP-Password | <password> | Argon2id access control (not E2E) |
curl --data-binary @secrets.env \
-H "Content-Type: text/plain" \
-H "NP-Expiry: 10m" \
-H "NP-Burn: 1" \
-H "NP-Password: hunter2" \
https://nullpaste.org/api/v1/pastesFetch a paste
GET /raw/<id> returns the paste as text/plain. GET /api/v1/pastes/<id> returns JSON with content plus metadata. Both follow the same rules:
- Plaintext only: an end-to-end encrypted paste returns
409from/raw— the server holds only ciphertext and never presents it as source code. - Password-protected pastes need
NP-Password: <password>(orAuthorization: Bearer <password>), otherwise401. - Burn-after-read pastes need
NP-Confirm-Burn: 1, otherwise409— reading one destroys it, so a straycurlcan never consume it by accident. A consumed paste returns410with{"error":"burned"}.
# plain paste
curl https://nullpaste.org/raw/AbC123xYz012
# password-protected paste
curl -H "NP-Password: hunter2" https://nullpaste.org/raw/AbC123xYz012
# burn-after-read paste — reading destroys it, so confirmation is explicit
curl -H "NP-Confirm-Burn: 1" https://nullpaste.org/raw/AbC123xYz012Delete a paste
curl -X DELETE \
-H "NP-Delete-Token: 9f2kQ7wLpX4mR8sT2v" \
https://nullpaste.org/api/v1/pastes/AbC123xYz012Returns 200 on success. The token is single-use by construction: deletion removes the paste and its token hash, so it cannot be replayed.
End-to-end encryption from your own client
API-created pastes are plaintext on the server unless your client encrypts them first. To make a paste the server cannot read, encrypt to the same versioned format the web editor uses — AES-256-GCM, fresh 12-byte IV, enc1:<b64url iv>:<b64url ciphertext> — and put the key in the URL fragment yourself. The fragment never reaches the server. Full spec on the security page.
JavaScript (Node 20+)
// Node 20+: E2E-encrypt before upload (same scheme the browser uses)
const { webcrypto: crypto } = require("node:crypto");
const b64url = (buf) => Buffer.from(buf).toString("base64url");
async function encryptAndPaste(plaintext) {
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 }, true, ["encrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
const body = `enc1:${b64url(iv)}:${b64url(ct)}`;
const res = await fetch("https://nullpaste.org/api/v1/pastes", {
method: "POST",
headers: { "Content-Type": "text/plain" },
body,
});
const paste = await res.json();
const keyB64 = b64url(await crypto.subtle.exportKey("raw", key));
return `${paste.url}#key=${keyB64}`; // full share link — key never uploaded
}Python
# pip install cryptography requests
import base64, os, requests
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def b64url(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def encrypt_and_paste(plaintext: str) -> str:
key = AESGCM.generate_key(bit_length=256)
iv = os.urandom(12)
ct = AESGCM(key).encrypt(iv, plaintext.encode(), None) # tag appended
body = f"enc1:{b64url(iv)}:{b64url(ct)}"
r = requests.post(
"https://nullpaste.org/api/v1/pastes",
data=body, headers={"Content-Type": "text/plain"})
paste = r.json()
return f"{paste['url']}#key={b64url(key)}" # key stays localnpaste shell function
# Add to ~/.bashrc or ~/.zshrc
npaste() {
curl -s --data-binary @"${1:--}" \
-H "Content-Type: text/plain" \
https://nullpaste.org/api/v1/pastes
}
# Usage:
# npaste file.txt — paste a file
# some-command | npaste — paste from stdinRate limits & abuse
Creation, reads, and deletes are rate-limited per client. The identifier is an HMAC-SHA256 hash derived from your IP — the raw IP is never stored or logged. Exceeding a limit returns 429 with a retry-after header. Report abusive pastes via the Report button on any paste page or the contact page.