World's first sealed Postgres
The Cellar
The Cellar is a sealed database for your app. You get a connection string, REST, live updates, and an import hatch. Paste a phpMyAdmin SQL dump (or CSV / JSON) and Zipper turns those tables into chambers. Browse and edit rows in Studio. Mint, revoke, and regenerate capability seals. Narrow a seal per user with scoped sessions (POST /session — tables and operations only). Every request runs under a 7.5s server budget with Server-Timing, and the official Cellar client retries 429/503 with Retry-After — no more client-side timeout hangs. Encryption and row rules stay on. SQL is Zipper’s dialect — it never hits the host Postgres parser. It is not Amazon RDS, not stock MySQL, and not a second Supabase. Live updates run on this Zipper process, not a world bus. Included on every Zipper plan.
Cloudflare analogue: Neon / Supabase Postgres
What it is
The Cellar is a sealed database for your app. You get a connection string, REST, live updates, and an import hatch. Paste a phpMyAdmin SQL dump (or CSV / JSON) and Zipper turns those tables into chambers. Browse and edit rows in Studio. Mint, revoke, and regenerate capability seals. Narrow a seal per user with scoped sessions (POST /session — tables and operations only). Every request runs under a 7.5s server budget with Server-Timing, and the official Cellar client retries 429/503 with Retry-After — no more client-side timeout hangs. Encryption and row rules stay on. SQL is Zipper’s dialect — it never hits the host Postgres parser. It is not Amazon RDS, not stock MySQL, and not a second Supabase. Live updates run on this Zipper process, not a world bus.
Closest analogue: Neon / Supabase Postgres. SLA 99.95% · p99 35ms. Admins create cellars, branches, seals, scoped sessions, RPC, and snapshots. Operators may run select seals.
- ▸Hatch: capability seal, fail-closed Origin
- ▸Scoped sessions — narrow tables + ops per user
- ▸7.5s request budget + Server-Timing, Retry-After on 503
- ▸Resilient client SDK — 429/503 auto-retry
- ▸Climate: AES-256-GCM and RLS always on
- ▸Import phpMyAdmin SQL / CSV / JSON
- ▸Studio: browse, insert, edit, delete rows
- ▸DELETE … RETURNING + multi-column order (PostgREST)
- ▸Mint · revoke · regenerate seals
- ▸SQL, REST, live hatch, and agent setup
- ▸Email/password seals for your users
How it works
The Cellar is your app database under Zipper. You get a connection string, REST, live updates, and POST /import for phpMyAdmin SQL (or CSV / JSON). Tables become chambers. Encryption and row rules stay on. SQL is Zipper’s dialect — it never hits the host Postgres parser. The hatch is a capability seal. Browser Origin is fail-closed (never *). It is not Amazon RDS, not stock MySQL, and not a second Supabase.
The climate cannot be turned off: AES-256-GCM around every row, default-deny RLS on every table (UPDATE/DELETE also need SELECT), fail-closed seals, overlay branches. Statement time, payload, and per-table inflight caps keep the rooms cool and stable.
The public `/demo` pad is a tasting room — SELECT only, per-IP, expires. Writes live behind a service or 1h user seal. Preview branches are overlays: forking copies nothing. Snapshots copy the resolved overlay onto an independent branch so later main writes cannot leak in.
Reliability contract: every request runs under a 7.5s server budget and answers a structured 503 cellar_timeout with Retry-After: 1 before any client 10s budget aborts — responses carry Server-Timing (resolve, run, total) on success and error alike. Scoped sessions (POST /session) narrow a seal to named tables and operations, subset-validated against the minter so a client can never escalate. The official Cellar client retries 429/503 with jittered backoff and honors Retry-After.
Use cases
Concrete ways teams use this service on day one.
Paste a DATABASE_URL
An app today stores DATABASE_URL=postgres://… for Neon or Supabase.
- Dashboard → The Cellar → Open a cellar named app. Copy the bootstrap DATABASE_URL once (the password is the seal).
- Use psql, node-postgres, or Prisma against db.tinyzipper.com:5432 (sslmode=require). Or POST HTTPS /wire and /query.
- SQL is parameterized. Comments and pg_catalog identifiers are refused. Scripts run at most eight statements, and every statement must pass the seal.
PostgREST / supabase-js Data API
A client calls GET /rest/v1/orders?status=eq.open with an anon key.
- Prefer the control-plane hatch https://tinyzipper.com/api/v1/cellar/{id} — REST lives at /rest/{table}. Vanity https://app--workspace.cellar.tinyzipper.com needs *.cellar TLS; on that host /rest/v1 rewrites to /rest.
- apikey and Authorization are an anon or user seal — never the service seal in a browser or mobile app.
- GET/POST/PATCH/DELETE with PostgREST filters, or=(a.eq.b,c.eq.d), embeds like select=id,items(sku), and Prefer: resolution=merge-duplicates. CSV insert is accepted. DELETE with Prefer: return=representation returns the removed rows (DELETE … RETURNING), and ?order=created_at.desc,id.asc sorts by multiple columns.
- Mint a 1h authenticated seal: POST /auth/v1/signup with email/password, or /auth/v1/token, or a BFF POST /auth/v1/token with the service seal and { "user_id" }. Recovery tokens are minted by a service seal — Zipper does not send mail. The hatch Origin is fail-closed; native clients omit Origin. Then narrow it per end user: POST /session with { user_id, tables: ["orders"], ops: ["select"] } — tables and ops must be a subset of the minter’s own seal.
- Watch a chamber: GET /live/orders?listen=1 on the control plane (vanity: /live/v1/orders). The first climate event is the cool snapshot this seal can see. Writes speak write; a committed BEGIN speaks hitch. Zipper process — not a global bus.
Chat room + climate hatch
A small team chat with email/password and a live chamber.
- Sign up: POST /auth/v1/signup { "email", "password" }. Store the access_token; it is the user seal.
- INSERT INTO rooms, then INSERT INTO room_members (self-join). INSERT INTO messages. Membership RLS hides other rooms.
- GET /live/v1/messages — cool snapshot. GET /live/v1/messages?listen=1 — climate. POST occupancy or whisper on that chamber. This is one Zipper process, not a global bus.
Feed an agent the hatch
You paste CELLAR_URL and a schema seal into Cursor (or any agent) and ask it to create a todos chamber.
- Mint a service seal with schema. Copy the dashboard handoff (CELLAR_URL + CELLAR_SEAL). Do not commit the seal.
- The agent GETs /agent (or /llms.txt) then PUT /setup { "chambers": [{ "name": "todos", "columns": [...], "owner": "auth.uid" }] }.
- Users sign in at /auth/v1/signup. Live hatch is GET /live/v1/todos. This is Zipper’s climate, not a Supabase MCP clone.
Import a phpMyAdmin dump
You already have a MySQL database. You exported it from phpMyAdmin and want it in The Cellar.
- In phpMyAdmin: Export → SQL (or CSV / JSON). Copy the file. Do not expect views, triggers, or stored procedures to come along.
- Dashboard → The Cellar → Import. A dialogue asks whether imported emails/phones must set a new password, and an optional HTTPS redirect for your app’s reset page. Or POST /import?reset_logins=1&redirect_to=https://app.example.com/reset.
- Someone signing in with an imported email or phone gets password_reset_required with recovery_token and redirect_to. Zipper does not send mail. Open redirect_to (or show a form), then POST /auth/v1/token with that token and a new password.
Preview data like a git branch
A feature branch should mutate orders without touching production rows.
- POST /api/v1/cellar/{id}/branches with name preview. Only changed rows are stored on the overlay.
- Query with options=branch=preview on DATABASE_URL, or { "branch": "preview" } on /query.
- Snapshot the overlay (resolved copy on an independent branch), or drop the preview branch. Production main is untouched.
Set it up in the dashboard
Dashboard → Services → The Cellar.
Copy DATABASE_URL, the climate hatch, and the agent handoff after minting a seal. Run SQL in the console. Fork overlay branches.
Encryption, RLS, overlay branches, and fail-closed seals stay on. They are the climate of the cellar.
API
Control-plane: GET|POST /api/v1/cellar · POST /query · /session · /wire · /rest/:table · /rpc/:name · /auth/:action · GET|PUT /live/:chamber · GET /agent · PUT /setup · POST /import · GET /export · DATABASE_URL db.tinyzipper.com:5432. Send Authorization: Bearer tz_live_YOUR_TOKEN.
Creates count against the plan quota. A 402 plan_limit means you are at the cap — upgrade or delete an unused resource.
Create a cellar
curl -sS -X POST https://tinyzipper.com/api/v1/cellar \
-H "Authorization: Bearer tz_live_YOUR_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"app","region":"af"}'DATABASE_URL (wire adapter)
psql "postgresql://service:zc_YOUR_SEAL@db.tinyzipper.com:5432/app?sslmode=require"
PostgREST
curl -sS "https://tinyzipper.com/api/v1/cellar/CELLAR_ID/rest/orders?status=eq.open" \ -H "apikey: zc_YOUR_SEAL" \ -H "Authorization: Bearer zc_YOUR_SEAL"
Sealed query
curl -sS -X POST https://tinyzipper.com/api/v1/cellar/CELLAR_ID/query \
-H "Authorization: Bearer zc_YOUR_SEAL" \
-H "Content-Type: application/json" \
-d '{"sql":"SELECT id, status FROM orders WHERE user_id = $1","params":["user_a"],"branch":"main"}'Mint a user session (BFF)
curl -sS -X POST https://tinyzipper.com/api/v1/cellar/CELLAR_ID/session \
-H "Authorization: Bearer zc_SERVICE_SEAL" \
-H "Content-Type: application/json" \
-d '{"user_id":"user_a"}'Climate hatch
const cellar = openHatch('https://app--workspace.cellar.tinyzipper.com', userSeal)
const messages = cellar.chamber('messages')
await messages.snapshot() // cool docs this seal can see
messages.watch().addEventListener('hitch', (ev) => console.log(JSON.parse(ev.data)))
await messages.put('m2', { body: 'stored cool', room_id: 'r1' })Import phpMyAdmin SQL
curl -sS -X POST "$CELLAR_URL/import?reset_logins=1&redirect_to=https://app.example.com/reset" \ -H "Authorization: Bearer $CELLAR_SEAL" \ --data-binary @wordpress.sql
Agent setup
curl -sS "$CELLAR_URL/agent" -H "Authorization: Bearer $CELLAR_SEAL"
curl -sS -X PUT "$CELLAR_URL/setup" \
-H "Authorization: Bearer $CELLAR_SEAL" -H "Content-Type: application/json" \
-d '{"intent":"todos","chambers":[{"name":"todos","owner":"auth.uid","columns":[{"name":"id","type":"text","pk":true},{"name":"user_id","type":"text","nullable":false},{"name":"title","type":"text"}]}]}'Public sandbox
const res = await fetch("https://tinyzipper.com/api/v1/cellar/demo", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ZIPPER_TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({ sql: "SELECT id, total FROM orders", branch: "preview" })
});
const json = await res.json();
if (!res.ok) throw new Error(json.error ?? res.statusText);
console.log(json);List the same resource in JavaScript
const res = await fetch("https://tinyzipper.com/api/v1/cellar", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.ZIPPER_TOKEN}`,
"Content-Type": "application/json"
}
});
const json = await res.json();
if (!res.ok) throw new Error(json.error ?? res.statusText);
console.log(json);Tips
The Cellar is not Amazon RDS. DATABASE_URL speaks Zipper’s sealed-query wire adapter, not stock Postgres on :5432 of the host.
Never log seal secrets. Rotate by minting a new seal and deleting the old prefix.
Do not orange-cloud db, pool, or *.cellar. DATABASE_URL is PGWire, not HTTP. REST sends CDN-Cache-Control: no-store.
The public demo cannot write without Authorization: Bearer demo-authenticated. Do not send a service seal from the browser. Mint a user seal in your BFF and keep the hatch origins explicit.
Browsers need Origin on SITE_URL, localhost, or CELLAR_ALLOWED_ORIGINS. curl, psql, and native apps omit Origin and enter with the seal.
A slow query returns 503 cellar_timeout after 7.5s with Retry-After: 1 instead of hanging your client for 10s. Read Server-Timing to see which phase stalled (resolve vs run vs save).
POST /session scopes a seal per end user: { user_id, tables, ops, ttl_seconds }. The scope is checked against the minter’s tables/ops — privilege escalation is refused. Short TTLs (60s–24h) are for client handoffs.
Use the official Cellar client for retry safety: 429 and 503 reads retry with jittered backoff and honor Retry-After; writes are never blind-retried.
Two JOINs nest as child arrays (PostgREST embed). Unqualified JOIN keeps unmatched parents; INNER JOIN drops them; LEFT JOIN keeps empty arrays. GROUP BY / HAVING is allowed without JOIN. BEGIN; …; COMMIT is a sealed script of at most eight statements, or separate statements with X-Cellar-Tx (PGWire sends it per socket). Stacked SQL runs as one script of at most eight statements; BEGIN/COMMIT/ROLLBACK are only allowed as a BEGIN block, and every statement must pass the seal. The live overlay expires in 60 seconds; hitch and webhooks fire on COMMIT; drafts stay on that hatch. The mutation journal is not stock Postgres WAL PITR. Climate is SSE on Zipper, not a global bus.
- ▸SOC 2
- ▸AES-256-GCM
- ▸Default-deny RLS
- ▸Sealed dialect
- ▸Scoped sessions
Runbook
Open a cellar. Copy DATABASE_URL (the password is the seal). Sign users in at /auth/v1/signup. Paste a phpMyAdmin SQL dump to POST /import — tables become chambers. Query with psql, REST, or POST /query. Narrow a seal per user with POST /session { user_id, tables, ops } — the subset is validated against the minter’s authority. Live data is GET /live/v1/{table}. Encryption and row rules stay on. Tenant SQL never reaches host Postgres. Every request runs under a 7.5s budget with Server-Timing; 429/503 answer with Retry-After and the client SDK retries reads for you.
Next: The Armory · All docs · Create a free account