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.
- CORS-open for every endpoint.
- Editable via a one-shot edit key returned at creation.
- Machine-readable OpenAPI 3.1 spec.
Base URL
https://pastepile.com
Endpoints at a glance
POST /api/public/pastes create a paste (returns edit_key, shown once)
GET /api/public/pastes list recent public pastes
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)
GET /api/openapi.json OpenAPI 3.1 specLimits & notes
- Total content per paste: 400 KB across all files.
- Files per paste: 1 to 10. Filenames: 1 to 80 chars.
- Best-effort rate limit: ~30 create / 60 update / 20 delete requests per IP per hour.
- 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":"..."}}
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. 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://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.
Plain-text body (shell-friendly)
POST https://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://pastepile.com/p/ab12cd3",
"raw_url": "https://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://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 listed pastes (no unlisted, encrypted, or sealed content).
GET https://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://pastepile.com/p/ab12cd3", "raw_url": "https://pastepile.com/raw/ab12cd3" }
],
"total": 123, "limit": 20, "offset": 0
}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://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://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://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://pastepile.com/raw/{slug}"
# or pipe straight into a file
curl -o file.txt "https://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://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://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://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, 403 sealed, 404 not found / protected, 409 custom_slug taken, 429 rate limited (with Retry-After), 500 server error.