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.
- ▸Rack — One duty in the twenty-container mesh (identity, records, kit, …). Docker isolates them when ARMORY_MESH=docker.
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.
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 '{"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"}'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 write warrant already fired. POST /warrants for a new write cartridge.
- ▸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.
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.
Next: The Crown · All docs · Create a free account