Files
palette/docs/API.md
T
poslop 9df6224a27
CI / test (push) Successful in 23s
CI / docker (push) Skipped
Result box: color-code by status + friendly error messages from error codes (#105)
- Backend create/can validation paths emit machine-readable error codes
  (slug_taken, slug_invalid, content_empty, content_too_large,
  expiry_invalid, rate_limited, ...) alongside the human message
- new.html JS maps codes to plain-language guidance with generic fallback
- Result card colored via --ok/--err/--warn left border (result-ok/err/warn)
- docs/API.md error section documents the code field
- Tests assert the code on every validation path
2026-09-09 17:28:25 -05:00

137 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.