New service
Using The Armory
How to use Zipper’s warrant mesh: create an app, sign someone in, spend a write cartridge, mint the next one. Spent warrants, hatch-bound sessions, musters, kit, and the roll — not a JWT farm.
Cloudflare analogue: Firebase Auth + Firestore (skill map only)
What this is
The Armory is a backend for iOS, Android, and the web that lives on this Zipper node. It is not Firebase, not a JWT farm, not FCM, and not Kubernetes.
You create an app, copy two keys, sign a person in, then send a warrant with every read or write. A write warrant is a spent cartridge: one fire, then mint another.
Words
These names are the product. Map them once, then the API reads plainly.
- ▸App — One backend for one product. Creating it prints an endpoint, an anon key, and a service key (once).
- ▸Anon key (za_anon_…) — Public key for the device or browser. It can sign people in. It cannot skip rules.
- ▸Service key (za_svc_…) — Server-only key. It opens every rack. Never put it in an IPA, APK, or public JS.
- ▸Session (za_sess_…) — Proof that a person signed in. It is bound to the Origin that minted it (the hatch).
- ▸Warrant (zw.…) — Permission to use one rack. A write warrant is a spent cartridge: it fires once, then you mint another.
- ▸Hatch — The browser Origin. A session minted at https://app.example stays there. Native apps omit Origin.
- ▸Muster — A named bag of sealed documents (like a table). Rules fail closed: public write is refused.
- ▸Kit — Uploaded files. Palace Guard scans bytes before they persist. Hostile kit returns 422.
- ▸Roll — Live events on this Zipper process as SSE. Not FCM, not APNs, not a world bus.
- ▸Spend Receipt (zr.…) — World-first: every spent write returns zr.… — a signed proof of what fired, on which hatch, chained to the audit head. Not a JWT. Verify with POST /receipts/verify.
- ▸Warrant Peel — World-first: attenuate a read warrant into a one-shot peel scoped to one muster. First list/get/look spends it — like a spent cartridge for reads.
- ▸Roll Witness — World-first: every Roll SSE event carries an HMAC witness. POST /roll/witness to prove the live event came from this node.
- ▸Vault Seal (zv.…) — World-first: service key mints zv.… — a notarized hash of the sealed vault for compliance. No blockchain. POST /seal/verify.
- ▸Sealed Muster (za1:) — World-first: every muster seals each document under its own AES-256-GCM key, wrapped by the vault — per-collection crypto-sealing no other backend ships.
- ▸Destruction Receipt (zd.…) — World-first: shred a muster (POST /musters/{name}/shred) and the sealed key is destroyed — zd.… is a signed, verifiable receipt that the ciphertext husk can never be read again. POST /shreds/verify.
- ▸Notary Chain — World-first: zv.… seals link seq → prev into an append-only vault timeline. Each seal proves it follows the previous one. No blockchain, no third party.
- ▸Zipper AI design — Optional prompt at create-app time. Designs musters, seed rows, and Scrolls on this node. Not a global schema migrator.
- ▸Hook — Outbound HTTPS POST on writes. Registered with the service key. Signed with X-Armory-Signature. SSRF-guarded egress.
- ▸Rack — One duty in the twenty-container mesh (identity, records, kit, …). Docker isolates them when ARMORY_MESH=docker.
- ▸Hatch URL — The live door for this app: /hatch/{name}. Same data as REST. Bind your website Origin here.
- ▸Wire — One POST /wire door: { do: "who" | "look" | "put" | "charge" }. Same warrants. Easier than many REST paths.
- ▸Look — A query: POST /look with { muster, where }. Returns only rows this warrant may see.
- ▸Scroll — A small function you write in the dashboard and charge from the app. It can who() and look(). Not a Worker isolate.
- ▸Devbook — Generated docs for this app: GET /docs, /openapi.json, /llms.txt. Always matches the live musters and scrolls.
Five steps
Work top to bottom. After step 3 you have a stored row. After step 4 you can write again.
- ▸1. Create an app Dashboard → Services → The Armory → Create app. Pick a kit. Paste your web origin if the client is a browser. Copy the endpoint, anon key, and service key. The service key is shown once.
- ▸2. Sign a person in POST https://tinyzipper.com/api/v1/armory/{id}/auth/signup with X-Armory-Key: za_anon_… and { email, password }. The JSON includes access_token, warrants.write, and warrants.read.
- ▸3. Write once POST /musters/{name} with Authorization: Bearer za_sess_… and X-Armory-Warrant: zw.… The write warrant is spent. A second use returns spent_cartridge.
- ▸4. Mint the next write POST /warrants with the session and { "verb": "write", "rack": "records" }. Keep the read warrant for GET /musters/{name}.
- ▸5. Stay on this hatch Browsers must send the same Origin that minted the session. Native apps omit Origin. A browser cannot replay a native session.
- ▸6. Fetch who you are GET /who (or POST /wire { "do": "who" }) with the session. That is the signed-in person. Use it for a profile screen.
- ▸7. Fetch rows (Look) POST /look with a read warrant and { "muster": "events", "where": { "title": "gate-up" } }. Service key can list people with GET /people.
- ▸8. Charge a Scroll Write a Scroll in the dashboard (who_am_i and look_first ship with a kit). POST /scrolls/{name} with a warrant. That is your “function”.
Kits
A kit plants musters so you can write on day one. Pick watch unless you already know you need lists or chat.
- ▸Watch — events — owner writes, signed-in users read. Incident logs, audit trails, “what just happened” feeds.
- ▸Roll — presence — owner writes, signed-in users read. Who is here on this node. Pair with GET /roll/presence.
- ▸Todos — todos — owner reads and writes. Private lists. Only the signed-in owner sees their rows.
- ▸Chat — rooms (signed-in read/write) and messages (owner write, signed-in read). Small rooms on this node. Fan-out is SSE here, not a world bus.
- ▸Blank — none until you POST /musters with the service key. Start empty and name your own musters.
Headers
Every client call uses some of these. The service key never leaves your server.
- ▸X-Armory-Key — za_anon_… on the device, or za_svc_… on the server.
- ▸Authorization — Bearer za_sess_… after sign-in.
- ▸X-Armory-Warrant — zw.… from signup or POST /warrants. Required on musters, kit, and roll.
- ▸X-Armory-Device — Optional stable device id. Binds the warrant to that device.
- ▸Origin — Required for browsers. Native clients omit it.
Copy-paste
Replace APP_ID and the keys printed at create-app. Signup returns warrants.write — spend it once.
Sign up
curl -sS -X POST https://tinyzipper.com/api/v1/armory/APP_ID/auth/signup \
-H "X-Armory-Key: za_anon_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"long-password"}'Spend a write warrant
curl -sS -X POST https://tinyzipper.com/api/v1/armory/APP_ID/musters/events \
-H "X-Armory-Key: za_anon_YOUR_KEY" \
-H "Authorization: Bearer za_sess_YOUR_SESSION" \
-H "X-Armory-Warrant: zw.YOUR_WRITE_WARRANT" \
-H "Content-Type: application/json" \
-d '{"data":{"title":"gate-up"}}'Mint the next write
curl -sS -X POST https://tinyzipper.com/api/v1/armory/APP_ID/warrants \
-H "X-Armory-Key: za_anon_YOUR_KEY" \
-H "Authorization: Bearer za_sess_YOUR_SESSION" \
-H "Content-Type: application/json" \
-d '{"verb":"write","rack":"records"}'Who am I
curl -sS https://tinyzipper.com/api/v1/armory/APP_ID/who \ -H "X-Armory-Key: za_anon_YOUR_KEY" \ -H "Authorization: Bearer za_sess_YOUR_SESSION"
Look (query)
curl -sS -X POST https://tinyzipper.com/api/v1/armory/APP_ID/look \
-H "X-Armory-Key: za_anon_YOUR_KEY" \
-H "Authorization: Bearer za_sess_YOUR_SESSION" \
-H "X-Armory-Warrant: zw.YOUR_READ_WARRANT" \
-H "Content-Type: application/json" \
-d '{"muster":"events","where":{"title":"gate-up"}}'Wire charge a Scroll
curl -sS -X POST https://tinyzipper.com/api/v1/armory/APP_ID/wire \
-H "X-Armory-Key: za_anon_YOUR_KEY" \
-H "Authorization: Bearer za_sess_YOUR_SESSION" \
-H "X-Armory-Warrant: zw.YOUR_READ_WARRANT" \
-H "Content-Type: application/json" \
-d '{"do":"charge","scroll":"who_am_i","body":{}}'Live URLs and generated docs
Every app has two doors on this node: REST /api/v1/armory/{id} and a live hatch /hatch/{name}. They share the same sealed state.
GET /hatch/{name}/docs is the Devbook for that app. OpenAPI is /openapi.json. Machine brief is /llms.txt. Those pages always match the musters and Scrolls you wrote in the dashboard.
Dashboard coding (Scrolls)
Inside the app you can write Scrolls — small functions your client charges. who() is the signed-in person. look(muster) returns rows this warrant may see. Keep Scrolls short. They are not Worker isolates.
A kit plants who_am_i and look_first so a mobile or web client can prove reads on day one. Add your own Scrolls when you need a query that is not a simple look.
Web profile
A Next.js site needs the signed-in person and their events.
- Create an app. Paste the site Origin as the hatch.
- Sign up from that Origin. Store access_token and warrants.read.
- GET /who for the profile. POST /look for events. Charge look_first if you want one round-trip.
iOS / Android
A Swift or Kotlin client has no Origin header.
- Omit Origin on signup so the hatch is native.
- Keep za_anon_ in the app. Keep za_svc_ on your BFF.
- GET /who, POST /look, POST /scrolls/who_am_i. Do not later call the same session from a website.
When it refuses
Fail-closed is the product. These codes are expected, not outages.
- ▸need_warrant — The data plane did not see X-Armory-Warrant. Sign in, then send warrants.write or mint one.
- ▸spent_cartridge — That warrant already fired (write or peel). POST /warrants for a new write cartridge, or attenuate a new peel.
- ▸muster_scope — Warrant is scoped to a different muster. POST /warrants/attenuate with the muster you need.
- ▸hatch_mismatch — Origin does not match the session hatch. Call from the same origin, or mint a native session without Origin.
- ▸fail_closed — Public write is refused. Use owner or user write rules. Service key to create musters.
- ▸cordon — Palace Guard held JSON string fields. Remove hostile content. EICAR in a field is refused.
- ▸quarantine — Palace Guard held a kit upload. Do not persist that file. Scan locally first.
- ▸rev_conflict — PATCH rev does not match the stored muster. GET the row, then PATCH with the current rev.
- ▸denied — Fail-closed rules blocked the actor. Sign in as the owner, or use the service key on the server.
- ▸muster_shredded — This muster was crypto-shredded — its za1 sealed key is destroyed. No one can read it again. Create a new muster for new data.
- ▸already_shredded — The muster key was already destroyed. GET /shreds for the tombstone and zd.… receipt.
- ▸armory_timeout — The request hit the server budget (7.5s) and was cut cleanly. Retry after 1s (Retry-After is set). Check Server-Timing to see which phase stalled.
- ▸armory_unconfigured — Armory storage is unreachable — fail closed, never a silent fallback. Fix ZIPPER_MASTER_KEY / storage config, then retry.
Honest limits
The roll is SSE on this Zipper process. It will not wake a phone via FCM or APNs.
Twenty Docker racks isolate duties on this Global Server node. They are not a Kubernetes replica set.
Zipper does not send email. Recovery tokens are returned to your backend for you to deliver.
What it takes: an Armory app, the anon key on the device, the service key only on your server, and a hatch Origin for websites. Web: fetch the hatch URL from the same Origin that signed in. iOS/Android: omit Origin. Never ship za_svc_ in an IPA or APK. Every look/put/charge sends X-Armory-Warrant. A write cartridge fires once. Mint another. Live is SSE on this node, not FCM. This hatch is a Zipper Global Server node. It is production HTTP (fail-closed, sealed, spent warrants). Plan quotas cap users and rows here.
Next: The Crown · All docs · Create a free account