Pastepile API
A small, public REST API for creating, reading, listing, updating, and deleting pastes from scripts, CI jobs, webhooks, and your terminal.
- No API key. No account. Anonymous by default.
- Optional, free API key adds per-key rate limits and a private, searchable namespace for your pastes.
- CORS-open for every endpoint.
- Editable via a one-shot edit key returned at creation.
- Machine-readable OpenAPI 3.1 spec.
Base URL
https://www.pastepile.com
Endpoints at a glance
POST /api/paste pipe endpoint: raw body in, paste URL out
POST /api/public/pastes create a paste (returns edit_key, shown once)
GET /api/public/pastes list pastes (public feed, or scope=mine with a key)
GET /api/public/pastes/{slug} fetch one paste as JSON
PATCH /api/public/pastes/{slug} update title/files (auth: edit_key)
PUT /api/public/pastes/{slug} alias of PATCH
DELETE /api/public/pastes/{slug} delete the paste (auth: edit_key)
GET /raw/{slug} fetch raw text (text/plain)
POST /api/keys generate an API key (optionally pro via pro_key)
POST /api/keys/revoke revoke a key with its revocation_secret
GET /api/openapi.json OpenAPI 3.1 specPipe from your terminal
One-liner paste sharing, no JSON required. Send anything to /api/paste and get the paste URL back as plain text. Pastes made this way are unlisted (link-only) and expire in 1 week; the one-time edit_key comes back in the X-Edit-Key response header.
# pipe a command's output dmesg | curl -sF 'p=<-' https://www.pastepile.com/api/paste # send a file curl -s --data-binary @error.log https://www.pastepile.com/api/paste # handy alias alias pp="curl -sF 'p=<-' https://www.pastepile.com/api/paste" history | pp
Limits & notes
- Total content per paste: 2 MB across all files for anonymous and free-key requests (4 MB for time-capsule pastes); pro keys allow 25 MB, enterprise 100 MB.
- Files per paste: 1 to 10. Filenames: 1 to 80 chars.
- Create rate limit (keyless): 30 / hour / IP, with a short burst cap of 30 / minute / IP. With an API key, creates are governed by the per-key limits 30 requests / minute and 500 creates / day. Update: 60/hour/paste/IP. Delete: 20/hour/paste/IP.
- Key generation: 5 new keys / day / IP. Key revocation attempts: 20 / hour / IP.
- The API creates non-encrypted pastes only. End-to-end encryption and time-capsule (drand timelock) pastes require the browser-held key and are not available over HTTP.
- Password-protected, encrypted, and sealed time-capsule pastes are not returned by the read endpoint.
- Errors are JSON:
{"error":{"code":"...","message":"..."}}
Limits (per key plan)
Keys are free and minted on the free plan. Linking a Pastepile Pro access key at creation upgrades the key to the pro plan; if the subscription ends, the key returns to free automatically. Anonymous keyless traffic keeps the "Limits & notes" rules above, including every expiry option.
| Plan | Creates / day | Requests / min | Max paste | No-expiry |
|---|---|---|---|---|
| Free | 500 | 30 | 2 MB | No |
| Pro | 2,000 | 120 | 25 MB | Yes |
| Enterprise | 10,000 | 600 | 100 MB | Yes |
On a free key, an explicit expiry: "never" is rejected with an actionable message rather than silently downgraded. Rate-limited responses expose X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers.
API keys (optional, free)
Keys are optional. The keyless API works exactly as documented here. A key buys two things: per-key rate limits instead of shared per-IP ones, and a private namespace: pastes saved with your key become listable and searchable via scope=mine, including unlisted ones. No sign-up required.
- Generate one on the keys page or via
POST /api/keys. - Send it as
X-API-Key: pk_live_...on requests. - You can also send it as
Authorization: Bearer pk_live_...on the create endpoint. - Generation returns BOTH the
keyand arevocation_secret(rvk_...), each shown once. We store only their SHA-256 hashes. - Keys are independent: generating a new key does NOT revoke any other key.
- Optional
pro_keyin the body: pass your Pro access key and the minted API key runs on the pro plan (the response includes"plan"). An invalid Pro access key fails loudly with402 invalid_pro_keyinstead of silently minting a free key. - If you lose the key, generate a new one. If you lose the revocation secret too, contact the operator with the key prefix.
# generate a key (returns key AND revocation_secret, both ONCE)
curl -X POST "https://www.pastepile.com/api/keys" \
-H "Content-Type: application/json" \
-d '{"label": "CI bot"}'
# use the key on a create
curl -X POST "https://www.pastepile.com/api/public/pastes" \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: text/plain" \
--data-binary "hello"
# revoke a key with its revocation secret
curl -X POST "https://www.pastepile.com/api/keys/revoke" \
-H "Content-Type: application/json" \
-d '{"revocation_secret": "rvk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}'
# 200 { "ok": true, "key_prefix": "pk_live_ab12", "label": "CI bot", ... }
# 404 { "ok": false, "message": "No matching key. ..." }Authentication (edit key)
Creating a paste returns an edit_key in the JSON response. It is shown once. The server only stores its SHA-256 hash; if you lose it you cannot recover it. The edit key is per-paste and is separate from your optional API key. Send it on update/delete one of three ways:
Authorization: Bearer <edit_key>
# or
X-Edit-Key: <edit_key>
# or in the JSON body
{ "edit_key": "<edit_key>", ... }POST /api/public/pastes
Create a paste. Two content types are accepted.
JSON body
POST https://www.pastepile.com/api/public/pastes
Content-Type: application/json
{
"title": "Optional title (<= 120 chars)",
"content": "string (single-file shortcut)",
"language": "plaintext | javascript | typescript | python | java | c | cpp | csharp | go | rust | ruby | php | html | css | sql | json | yaml | bash | markdown",
"files": [ { "name": "main.js", "language": "javascript", "content": "..." } ],
"expiry": "never | 10m | 1h | 1d | 1w | 1mo | burn",
"visibility": "public | unlisted",
"password": "optional server-side password",
"custom_slug": "optional vanity URL: 3-40 chars, lowercase letters, digits, single hyphens"
}Provide either content (single file) or a non-empty files array. Default expiry is 1w, default visibility is public.burn forces unlisted and deletes the paste on first read. The full list of syntax-highlighting languages lives at /languages.
Plain-text body (shell-friendly)
POST https://www.pastepile.com/api/public/pastes Content-Type: text/plain <the paste content>
Response body is just the paste URL followed by a newline.
Response (JSON body)
200 OK
{
"slug": "ab12cd3",
"url": "https://www.pastepile.com/p/ab12cd3",
"raw_url": "https://www.pastepile.com/raw/ab12cd3",
"edit_key": "kK9...32-char-secret",
"edit_key_notice": "Save this edit_key now - it is shown once and is required to update or delete this paste."
}Create with multiple files
curl -X POST "https://www.pastepile.com/api/public/pastes" \
-H "Content-Type: application/json" \
-d '{
"title": "Two files",
"expiry": "1d",
"visibility": "public",
"files": [
{ "name": "main.js", "language": "javascript", "content": "console.log(1)" },
{ "name": "README.md","language": "markdown", "content": "# hi" }
]
}'GET /api/public/pastes
List recent pastes. Default scope is the public feed (no unlisted, encrypted, or sealed content). With scope=mine and an API key, it lists YOUR pastes instead: the ones saved with that key, newest first, including unlisted ones. search= filters either scope by title, filename, and content keywords.
GET https://www.pastepile.com/api/public/pastes?limit=20&offset=0&search=
200 OK
{
"items": [
{ "slug": "ab12cd3", "title": "...", "language": "javascript",
"preview": "...", "views": 4, "file_count": 1,
"created_at": "...", "url": "https://www.pastepile.com/p/ab12cd3", "raw_url": "https://www.pastepile.com/raw/ab12cd3" }
],
"total": 123, "limit": 20, "offset": 0
}
# your private namespace (requires X-API-Key or Authorization: Bearer)
GET https://www.pastepile.com/api/public/pastes?scope=mine&search=runbook
# 200 { "items": [...], "total": 3, "limit": 20, "offset": 0, "scope": "mine" }
# 401 without a valid key: scope=mine requires authenticationIsolation is per key: one key can never list or search another key's pastes. Previews are never returned for encrypted, password-protected, or burn-after-read pastes.
GET /api/public/pastes/{slug}
Fetch one paste as JSON. Increments the view counter and consumes burn-after-read pastes (same semantics as the website).
GET https://www.pastepile.com/api/public/pastes/{slug}
200 OK
{
"slug": "ab12cd3",
"title": "Optional title",
"language": "javascript",
"files": [ { "name": "main.js", "language": "javascript", "content": "console.log('hi')" } ],
"views": 4,
"created_at": "2026-06-14T12:34:56Z",
"expires_at": "2026-06-21T12:34:56Z",
"burn_after_read": false
}Returns 404 for missing/expired/password-protected/encrypted pastes, and 403 for sealed time capsules. Password hashes and edit keys are never returned.
PATCH /api/public/pastes/{slug} (or PUT)
Update title/files of an existing paste. Requires the edit key.
curl -X PATCH "https://www.pastepile.com/api/public/pastes/<slug>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <edit_key>" \
-d '{
"title": "New title",
"files": [ { "name": "main.js", "language": "javascript", "content": "console.log(2)" } ]
}'
# 200 OK
# { "ok": true, "version_no": 3 }DELETE /api/public/pastes/{slug}
Permanently delete a paste. Requires the edit key.
curl -X DELETE "https://www.pastepile.com/api/public/pastes/<slug>" \
-H "Authorization: Bearer <edit_key>"
# 200 OK
# { "ok": true }GET /raw/{slug}
Fetch the raw text of a paste as text/plain; charset=utf-8 with X-Content-Type-Options: nosniff. Ideal for piping into shell tools.
curl "https://www.pastepile.com/raw/{slug}"
# or pipe straight into a file
curl -o file.txt "https://www.pastepile.com/raw/{slug}"Shell one-liner: pipe stdin, get a URL
Drop into .bashrc / .zshrc:
pv() {
curl -s --data-binary @- \
-H "Content-Type: text/plain" \
"https://www.pastepile.com/api/public/pastes"
}
cat error.log | pv
git diff | pvEmbeds
Every non-encrypted paste has a bare embed page at /embed/{slug}:
<iframe src="https://www.pastepile.com/embed/<slug>" style="width:100%;height:420px;border:0;border-radius:12px;overflow:hidden" loading="lazy" title="Pastepile paste" ></iframe>
OpenAPI
Machine-readable spec for codegen, Postman, Insomnia, or your favorite client generator:
GET https://www.pastepile.com/api/openapi.json
Errors
Errors are JSON with a stable code and a human message: 400 bad input, 401 missing/invalid edit key or API key, 402 invalid_pro_key on key generation, 403 sealed, 404 not found / protected, 409 custom_slug taken, 413 payload too large for your plan, 422 content rejected by the abuse filter, 429 rate limited (with Retry-After and X-RateLimit-* headers), 500 server error.