ppush

API documentation

Create pushes from scripts — provisioning, onboarding, CI — while keeping end-to-end encryption.

Authentication

Generate a personal token in Account → API tokens (shown only once), then pass it as a header:

Authorization: Bearer ppush_xxxxxxxxxxxxxxxxxxxx

The API only accepts already-encrypted content. The server never encrypts anything itself — that is what guarantees zero-knowledge. Use the provided CLI (recommended) or implement the format below.

The CLI (recommended)

tools/ppush-cli.mjs (in the repository) encrypts locally then calls the API. Node ≥ 20, no dependencies.

export PPUSH_URL=https://ppush.online
export PPUSH_TOKEN=ppush_xxx

# password, 3 views max, 7 days
ppush-cli.mjs --password "S3cret!" --views 3 --days 7

# text from stdin
cat config.yml | ppush-cli.mjs --text -

# file with passphrase and private note
ppush-cli.mjs --file dump.sql.gz --passphrase "sesame" --note "backup for Bob"

# → prints the full URL, decryption key included in the fragment

Options: --days N · --views N · --passphrase X · --note "…" · --no-retrieval-step · --no-deletable

Endpoints

POST/api/pushesBearer or session

Creates a push. JSON body:

{
  "kind": "PASSWORD" | "TEXT" | "FILE" | "URL",
  "ciphertext": "<base64>",          // encrypted payload (see format)
  "blobPath": "xxx.bin",             // FILE only (see POST /api/blobs)
  "passphrase": "optional",
  "expireAfterDays": 7,              // 1..30
  "expireAfterViews": 5,             // 1..50
  "retrievalStep": true,
  "deletableByViewer": true,
  "note": "optional private reference"
}

Response 201: { "slug", "url", "expiresAt", … }. The link to share is url + "#" + base64url_key — only you know the key.

POST/api/blobsBearer or session

Upload of a file's encrypted blob (raw body, max ~90 MB with an account). Response: { "blobPath": "xxx.bin", "size": 123 } to pass next to POST /api/pushes.

GET/api/pushes?page=1&filter=activeBearer or session

Paginated list of your pushes. filter: active | expired | (omitted = all).

GET/api/pushes/:slugBearer or session

Push details + full audit log (events: CREATED, VIEW, FAILED_PASSPHRASE, EXPIRED, OWNER_DELETE, VIEWER_DELETE, with IP and user-agent).

DELETE/api/pushes/:slugBearer or session

Expires the push immediately (permanent purge of the payload).

GET/api/p/:slugpublic

Public metadata of a push (kind, expired or not, passphrase required…). Does not consume a view.

POST/api/p/:slug/revealpublic

Reveals the ciphertext and consumes a view. Body: { "passphrase": "…" } if required. For a FILE, also returns a single-use viewToken for GET /api/p/:slug/blob?vt=….

POST/api/p/:slug/burnpublic

Destruction by the recipient (if allowed at creation).

GET/api/storagepublic

Transparent fair use of file storage: { "availableBytes", "upcoming": [{ "bytes", "at" }] } upcoming lists upcoming space releases (cumulative, by latest expiry date). When authenticated, adds "user": { "usedBytes", "availableBytes", "quotaBytes" } (personal active-files quota). An upload refused for lack of space returns 507 with an estimate of the next window.

Encryption format (v1)

If you do not use the CLI, produce the ciphertext as follows (AES-256-GCM, WebCrypto or equivalent):

Key      : 32 random bytes → base64url → link fragment (#…)
Payload  : JSON { "t": "PASSWORD|TEXT|URL", "d": "<secret>" }
           (FILE: { "t":"FILE", "d":"", "name", "mime", "size" })

ciphertext = 0x01 ‖ IV(12 bytes) ‖ AES-256-GCM(payload, AAD="ppush:v1:meta")
           → base64 in the "ciphertext" field

Files (blob): 8 MiB chunks, each framed:
  [length u32 BE] [IV 12 bytes] [ct+tag]
  AAD of chunk i: "ppush:v1:<i>:<1 if last, else 0>"

Limits & errors

  • Error responses: { "error": "message" } with the appropriate HTTP status (400 validation, 401 auth, 403 forbidden, 404 not found, 409 conflict, 410 expired, 413 too large, 429 rate limit).
  • Per-IP rate limiting on sensitive endpoints (login, passphrase, reveal). On a 429, wait and retry.
  • Encrypted text payload: 1024 KB max · files: 90 MB max (account) · retention: 30 days / 50 views max. Without an account: 7 days / 5 views, files 10 MB.
  • Fair-use file storage: shared global space (see GET /api/storage) + personal quota of 3 GB of active files per account. When full → 507 with an estimate of upcoming space releases.
  • An API token has the rights of its owner (creation, listing, expiration of their pushes). Revocable at any time from the account page.

See also the About page and token management.