storage-p

API documentation

The same REST API the web client runs on. Every item is encrypted in your browser before it leaves the device — the server only ever sees ciphertext and has no way to read it. Use it to build your own clients or to give an integration scoped, confirmable access to a few items. Base URL: https://storage-p.com

Guides

End-to-end walkthroughs of what storage-p actually does — grounded in the real feature set, nothing invented.

Self-hosting on your own server

Run storage-p as a Docker container behind Caddy on your domain. Caddy serves the static client and reverse-proxies /api to the backend; the SQLite database is encrypted at rest with SQLCipher. You operate it end to end — nobody else holds the keys.

Storing & generating SSH / TLS keys

Generate Ed25519 SSH keys (OpenSSH format) and self-signed TLS certificates in the Generators tab, or upload existing key/cert files (up to 1 MB). The private material is encrypted in the browser before upload, so it never exists in plaintext on the server.

One-time, burn-after-read links

Share a single item or a whole folder via a link whose decryption key lives only in the URL fragment (#…) — the part browsers never send to the server. Set a TTL and a view limit; after the last view the data can no longer be opened.

Scoped API tokens with confirmation

Create a token that can read only the items or projects you whitelist. Turn on per-read confirmation and each access pauses until you approve it from the in-app bell or a Telegram message; add a rate limit and an expiry. The integration receives the still-encrypted item plus a one-time access key to decrypt it locally.

Projects & team access

Turn a folder into a project with its own key. Grant another user read or write access and the project key is sealed to their public key (X25519), so they work with the latest items without the server ever seeing a readable copy. Revoke any grant at any time.

Built-in TOTP / 2FA

Store a login’s TOTP secret next to it and read the live rotating code in the same place — storage-p doubles as your authenticator. You can also protect your own account with TOTP two-factor.

Importing from another manager

Bring in a Bitwarden JSON, a KeePass CSV, or any CSV with name/username/password/url/notes columns. Parsing and encryption run on your device, so entries are re-encrypted under your key and never uploaded in the clear.

API documentation · API

Authentication

Obtain an access token via register/login, then send it as a Bearer token. Access tokens are short-lived (15 min); refresh with the refresh token.

Authorization: Bearer <access_token>

Auth

Sign up, sign in and keep the session alive. The master password never leaves the browser — only an Argon2id auth-hash is sent.

POST /api/v1/auth/register
Create an account. The client picks the KDF parameters and uploads its already-encrypted private key.
Body{ email, master_password, kdf_salt_b64, kdf_memory_kib, kdf_iters, kdf_parallelism, pubkey_b64, privkey_enc_b64 }
Response{ access_token, refresh_token, user_id, kdf_* }
POST /api/v1/auth/login
Sign in. Returns the tokens plus the KDF salt the client needs to derive the vault key.
Body{ email, master_password, totp_code? }
Response{ access_token, refresh_token, user_id, kdf_*, totp_required }
POST /api/v1/auth/refresh
Exchange a refresh token for a fresh, short-lived access token.
Body{ refresh_token }
Response{ access_token, refresh_token }
POST /api/v1/auth/logout Bearer
Revoke the current refresh token.
Response{ ok }
POST /api/v1/auth/totp/setup Bearer
Turn on TOTP two-factor for the account.
Body{ secret_b32, current_code }
Response{ ok }

Vault

CRUD over your items. Everything goes in and out already encrypted; the server stores and returns ciphertext only.

GET /api/v1/vault Bearer
List every item (ciphertext) in your vault.
Response[ { id, meta_enc_b64, body_enc_b64, version, created_at, updated_at } ]
POST /api/v1/vault Bearer
Add an item. meta/body are already encrypted client-side before upload.
Body{ meta_enc_b64, body_enc_b64, blind_index_b64? }
Response{ id, ... }
GET /api/v1/vault/:id Bearer
Fetch a single item by id.
PUT /api/v1/vault/:id Bearer
Update an item; expected_version guards against overwriting a newer change.
Body{ meta_enc_b64?, body_enc_b64?, expected_version }
DELETE /api/v1/vault/:id Bearer
Move an item to the trash (soft-delete, reversible).
GET /api/v1/vault/trash Bearer
List items currently in the trash.
POST /api/v1/vault/:id/restore Bearer
Restore an item from the trash.
DELETE /api/v1/vault/:id/purge Bearer
Permanently delete an item — cannot be undone.

Sharing

Two ways to hand a secret to someone else: a one-time burn-after-read link, or an end-to-end sealed-box delivery to another user.

POST /api/v1/share Bearer
Create a one-time link. The decryption key lives in the URL fragment and never reaches the server.
Body{ payload_b64, nonce_b64, ttl_secs, max_views }
Response{ id, expires_at }
GET /api/v1/share/:id
Open a one-time link. Burns after the allowed number of views.
Response{ payload_b64, nonce_b64 }
GET /api/v1/users/lookup?email= Bearer
Find a user’s public key by email so you can share end-to-end with them.
Response{ user_id, pubkey_b64 }
POST /api/v1/shares/user Bearer
Send an item to another user, sealed to their public key — only they can open it.
Body{ to_email, payload_b64 }
GET /api/v1/shares/incoming Bearer
List items other users have shared with you.
DELETE /api/v1/shares/:id Bearer
Remove an incoming or outgoing share.

Tokens & confirmations

Scoped tokens let an integration read selected items, optionally gated by your in-app/Telegram confirmation.

GET /api/v1/tokens Bearer
List your API tokens.
POST /api/v1/tokens Bearer
Create a scoped token for an integration — pick which items it may read and whether each read needs confirmation.
Body{ name, item_ids[], require_confirmation, rate_limit_per_hour, ttl_secs }
Response{ id, token, expires_at }
DELETE /api/v1/tokens/:id Bearer
Revoke a token immediately.
GET /api/v1/confirmations/pending Bearer
List access requests waiting for your approval.
POST /api/v1/confirmations/:id/resolve Bearer
Approve or deny a pending request.
Body{ decision: "approve"|"deny" }
GET /api/v1/api/vault/:id Bearer (scoped token)
How an integration reads a scoped item. If confirmation is required, the first call returns a confirmation_id to poll until you approve.

Scoped API tokens (for integrations)

Create a scoped token in the UI (API tokens). It can read only whitelisted items, and reads can require confirmation. Flow: first GET returns a confirmation_id with status pending_confirmation; the owner approves in-app or via Telegram; repeat the GET with ?confirmation_id= to receive the (still client-encrypted) item.

Notes

meta_enc / body_enc are nonce(24)‖ciphertext, encrypted with the vault key derived from the master password. The server cannot decrypt them.