Pages CRUD

These endpoints back the dashboard. They accept either a JWT Authorization: Bearer header or an op_… API token. Files are sent as multipart form fields.

Auth. All endpoints require authentication via get_current_user — JWT cookie, JWT bearer, or API token. Users only see and modify their own pages.

GET /pages

List all pages owned by the current user, newest first.

Response 200

[
  {
    "id": "Ab12Cd34",
    "name": "Hello World",
    "visibility": "public",
    "passcodes": ["letmein"],
    "allowed_emails": [],
    "default_file": "index.html",
    "created_at": "2026-05-03T17:42:11",
    "updated_at": "2026-05-03T17:42:11"
  }
]

cURL

curl https://outpost.click/pages \
  -H "Authorization: Bearer op_YOUR_TOKEN"

JavaScript (browser)

const res = await fetch('/pages', {
  headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
const pages = await res.json();

Python

import requests
r = requests.get(
    "https://outpost.click/pages",
    headers={"Authorization": f"Bearer {token}"},
)
r.raise_for_status()
pages = r.json()
Decrypted passcodes. The passcodes array in the response is the decrypted plain-text list — it's intended for the owner's dashboard to display. Don't expose this response to viewers.

POST /pages

Create a new page. Provide either a file upload or a source_url, not both. The server generates an 8-character page id and detects the default file (see default-file detection).

Form fields

FieldTypeRequiredNotes
nametextyesDisplay name; up to 200 chars.
visibilitytextyes One of private, shared, public.
passcodestextno Comma-separated list. Encrypted with Fernet derived from jwt_secret.
allowed_emailstextno Comma-separated list. Used for shared visibility.
filefileone of ZIP archive (≤ 50 MB) or a single .html file.
source_urltextone of http(s)://…; the page and same-domain assets are fetched.

Examples

ZIP upload — public, no passcode
curl -X POST https://outpost.click/pages \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "name=My Site" \
  -F "visibility=public" \
  -F "file=@./site.zip"
Single HTML — private
curl -X POST https://outpost.click/pages \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "name=Notes" \
  -F "visibility=private" \
  -F "file=@./notes.html"
Public with two passcodes
curl -X POST https://outpost.click/pages \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "name=Friends Only" \
  -F "visibility=public" \
  -F "passcodes=red-rover,blue-jay" \
  -F "file=@./site.zip"
Shared with a team
curl -X POST https://outpost.click/pages \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "name=Team Wiki" \
  -F "visibility=shared" \
  -F "allowed_emails=alice@acme.com,bob@acme.com" \
  -F "file=@./wiki.zip"
Import from URL
curl -X POST https://outpost.click/pages \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "name=Mirror" \
  -F "visibility=public" \
  -F "source_url=https://example.com"

Response 200

{
  "id": "Ab12Cd34",
  "name": "My Site",
  "visibility": "public",
  "passcodes": [],
  "allowed_emails": [],
  "default_file": "index.html",
  "created_at": "2026-05-03T17:42:11",
  "updated_at": "2026-05-03T17:42:11"
}

Errors

  • 400 Invalid visibility
  • 400 Either file or source_url must be provided
  • 400 Provide either file or source_url, not both
  • 400 Upload exceeds maximum size of 50MB
  • 400 Total page size exceeds maximum of 100MB
  • 400 Invalid zip file
  • 400 Only .zip archives or .html files are supported
  • 400 URL must return HTML content
  • 401 Missing credentials
Failed uploads roll back. If file processing throws a StorageError after the pages row was inserted, the row is deleted before the error is returned — you won't see orphan page records.

PUT /pages/{page_id}

Update any subset of metadata, and optionally replace the page files. Pass only the fields you want to change. Any uploaded file fully replaces the previous content.

Form fields (all optional)

FieldBehavior
nameReplace the page name.
visibilityReplace; must still be private/shared/public.
passcodes Comma-separated, replaces the entire list. Pass an empty string to clear.
allowed_emails Comma-separated, replaces the entire list.
file Optional ZIP or HTML upload. Replaces all stored files; the previous content is deleted before the new files are written.

Examples

Rename + flip to private
curl -X PUT https://outpost.click/pages/Ab12Cd34 \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "name=My Renamed Site" \
  -F "visibility=private"
Replace passcodes
curl -X PUT https://outpost.click/pages/Ab12Cd34 \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "passcodes=newpass1,newpass2"
Clear all passcodes
curl -X PUT https://outpost.click/pages/Ab12Cd34 \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "passcodes="
Replace files
curl -X PUT https://outpost.click/pages/Ab12Cd34 \
  -H "Authorization: Bearer op_YOUR_TOKEN" \
  -F "file=@./new-build.zip"

Response 200

{
  "id": "Ab12Cd34",
  "name": "My Renamed Site",
  "visibility": "private",
  "passcodes": ["newpass1", "newpass2"],
  "allowed_emails": [],
  "default_file": "index.html",
  "created_at": "2026-05-03T17:42:11",
  "updated_at": "2026-05-03T18:01:55"
}

Errors

  • 400 Invalid visibility
  • 400 any storage error from the upload (size, zip integrity).
  • 404 Page not found (also returned when you don't own the page — this is intentional, to avoid leaking the existence of other users' pages).
The updated_at column is set to datetime.utcnow() on every successful PUT.

DELETE /pages/{page_id}

Delete the page and its files. The page_files rows are deleted explicitly; the pages row is then deleted (cascading would also clean up files on its own).

curl -X DELETE https://outpost.click/pages/Ab12Cd34 \
  -H "Authorization: Bearer op_YOUR_TOKEN"

200 OK
{ "deleted": "Ab12Cd34" }

Errors

  • 404 Page not found (page doesn't exist, or you don't own it).

Visibility rules

ValueWho can view /p/<id>
private Only the owner. Anyone else gets the access gate; if any passcode is set, supplying it grants access for 24 hours.
shared The owner, anyone whose logged-in email is in allowed_emails, or anyone who supplies a valid passcode.
public Anyone — no login required. If a passcode is set, the access gate is shown until the visitor supplies it.

See Page serving for the gate workflow, the page_access_<id> cookie, and SPA-style fallback to the default file.