# Palette API All endpoints are JSON unless noted. The web UI is served from the same port. ## Create paste ```bash curl -X POST http://localhost:8080/api/pastes \ -H "Content-Type: application/json" \ -d '{ "content": "print(hello)", "title": "my snippet", "language": "python", "expires_in": "168h", "password": "optional", "custom_slug": "optional", "burn_after_read": false, "burn_after_reads": 1, "visibility": "public" }' ``` - `expires_in` is a Go duration string (`90m`, `6h`, `336h`). Omit for no expiry. - `visibility` is `public` or `unlisted`. - Alternatively (or additionally), `public` may be sent as a boolean (#83): `false` maps to `unlisted` and `true` maps to `public`. When both fields are present, the boolean `public` takes precedence over the string `visibility`. Omitting both defaults to `public`. - `burn_after_reads` sets how many reads the paste survives (default 1 when `burn_after_read` is true). A read is counted per unique viewer session; the same viewer returning within 15 minutes does not count again. - Response includes `id`, `url`, `raw_url`, `api_url`, `expires_at`, `created_at`, and a one-time `deletion_token`. Errors return JSON with a human-readable `error` message plus a machine-readable `code` the web UI maps to plain-language guidance (#105): | Code | Status | Meaning | |---|---|---| | `content_empty` | 400 | content is required | | `slug_invalid` | 400/409 | custom slug malformed | | `slug_taken` | 409 | custom slug already in use | | `slug_reserved` | 409 | custom slug is reserved | | `expiry_invalid` | 400 | expires_in out of 1 minute – 1 year range | | `content_too_large` | 413 | content or body exceeds the size cap | | `rate_limited` | 429 | too many requests; see `Retry-After` | Other statuses: `400` invalid body, `401` password required, `404` paste expired/burned/gone. Unknown codes should be treated as a generic failure. ## Get paste ```bash curl http://localhost:8080/api/pastes/{id} # password-protected pastes: curl "http://localhost:8080/api/pastes/{id}?password=secret" # or via header: X-Paste-Password: secret ``` The response includes `reads_remaining` (`null` when no read budget is set). ## Raw content ```bash curl http://localhost:8080/raw/{id} ``` Raw reads count against a burn-after-read budget, same as page views. ## Delete ```bash # soft delete (requires the deletion token from the create response) curl -X DELETE -H "Authorization: Bearer TOKEN" http://localhost:8080/api/pastes/{id} # ...or via query param; the creator browser (viewer cookie) may also delete without a token curl -X DELETE "http://localhost:8080/api/pastes/{id}?token=TOKEN" # hard delete immediately (requires the one-time deletion token) curl -X DELETE "http://localhost:8080/api/pastes/{id}/redeem?token=TOKEN" ``` ## Lists ```bash curl "http://localhost:8080/api/public?limit=25&offset=0" # public history curl http://localhost:8080/api/mine # this browser's pastes (viewer cookie) ``` ## Language detection ```bash curl -X POST http://localhost:8080/api/guess-language \ -H "Content-Type: application/json" \ -d '{"content": "package main"}' ``` ## Cans (bundles of items) A can bundles multiple text items (and files) into one shareable page at `/can/{id}`. Password, expiry, visibility, custom slug, and viewer-scoped deletion work exactly like pastes; cans appear as normal rows (with a `can` badge) in `/api/public` and `/api/mine`. ```bash curl -X POST http://localhost:8080/api/pastes/can \ -F "title=My bundle" \ -F "expires_in=48h" \ -F "custom_slug=my-bundle" \ -F 'json_items=[{"title":"notes.txt","content":"some notes"}]' \ -F "files=@screenshot.png" \ -F "files=@log.txt" curl http://localhost:8080/api/cans/{id} curl http://localhost:8080/api/cans/{id}/items/{item_id} curl -X DELETE http://localhost:8080/api/cans/{id} # creator browser only (vwr cookie) ``` Password-protected cans use the same unlock flow as pastes: `POST /can/{id}` with the password sets an HMAC-bound cookie; item fetches accept the cookie as well as the `X-Paste-Password` header / `?password=` query param. ## Web pages - `/new` — create a paste - `/history` — public paste history - `/mine` — pastes created from this browser - `/{id}` — view a paste - `/unlock/{id}` — password gate for protected pastes - `/raw/{id}` — raw content with original content type ## Expiry and deletion - Expired pastes are soft-deleted by a background sweeper (runs every minute). - Soft-deleted pastes are hard-deleted after a 7-day grace period. - Deletion tokens allow immediate hard delete. - Burn-after-read pastes are soft-deleted once the read budget is exhausted.