HostedWitbitz operates the runtime; builders get tenant keys during private beta.
EnterpriseThe same runtime runs inside a customer cloud boundary.
Air-gapNo external egress; model and replacement services stay local.
BridgeSovereign Spaces collaborate without merging tenants or trust domains.
| Option | Status | Boundary | Best fit |
|---|---|---|---|
| **Hosted private beta** | Live, invite-only | Witbitz-operated cloud | Early builders and platform integrations. |
| **Enterprise VPC / on-prem** | Runnable today | Customer-controlled cloud or network | Enterprise buyers who want their own model, storage, IdP, and ops. |
| **Air-gap** | Runtime switch exists; self-hosted model quality remains the main work | No external egress | Regulated, defense, government, and high-control deployments. |
| **Bridge** | Early protocol preview | Multiple sovereign Spaces or external parties | Cross-organization collaboration without merging tenants or trust domains. |
## Hosted
Hosted is the default private-beta path. Witbitz operates the runtime and exposes the tenant-keyed `/v1` API to invited
integrators. The privacy claim rests on the checkable hosted evidence:
- admission gate checks
- sealed storage transparency
- browser egress lock
- signed build certificate
- reproducible render build
Read: [Hosted private beta](./hosted-private-beta.md), [Verify it yourself](./verify.md)
## Enterprise VPC / on-prem
In a customer boundary, the same runtime points its seams at customer-controlled services:
- model endpoint
- object store
- record store
- secrets backend
- identity provider
- logs, backup, retention
The trust story shifts from "verify Witbitz's hosted deployment" to "verify the image you run and control the
boundary yourself."
Read: [Enterprise and on-prem](./on-prem.md)
## Air-gap
`AIRGAP=1` refuses outbound paths in the browser and server. External tools are disabled unless replaced with local
services. The model must also be in-boundary for a true air-gap.
Read: [Air-gap mode](./air-gap.md)
## Bridge
The Bridge is not a deployment target for one app. It is the cross-boundary protocol for governed collaboration
between sovereign Spaces, including external parties that do not run Witbitz internally.
Read: [The Bridge](./the-bridge.md)
## Choosing a mode
| Need | Start with |
|---|---|
| Build against the API quickly | Hosted private beta |
| Keep data and model calls inside your tenant | Enterprise VPC / on-prem |
| Prove no external egress | Air-gap |
| Collaborate with another organization without sharing a tenant | Bridge |
| Verify hosted privacy claims | Hosted + verify page |
| Remove Witbitz as an operator | On-prem |
---
# Hosted private beta
Hosted Witbitz is invite-only while the platform contract settles. The docs and API contracts are public; tenant keys
are gated.
This page is the operating model for builders using Witbitz-hosted infrastructure.
---
## What you get
| Surface | Status |
|---|---|
| Spaces app | Live |
| `/space` async runtime | Live |
| Tenant-keyed `/v1` API | Private beta |
| OpenAPI production spec | Public |
| Full design OpenAPI | Public, status-labelled |
| Owner rule | Built, opt-in, not default |
| Bridge API | Early preview |
## Access
The access page can redeem an access code and create a tenant key pair:
```text
https://witbitz.chat/access
```
Keys are shown once. Secret keys are server-side only. Publishable keys are browser-safe identifiers for public reads
and funnel-entry writes, but they do not authenticate an end user.
If you do not have an access code, email:
```text
hello@witbitz.chat
```
## API contract
The production `/v1` API is documented openly:
```text
https://api.witbitz.chat/v1/openapi.json
https://api.witbitz.chat/v1/openapi.full.json
https://api.witbitz.chat/v1/
```
During private beta, the contract may change in coordination with invited integrators. Treat `openapi.json` as the
production callable surface and `openapi.full.json` as the broader design map.
Read: [Platform API](./api-reference.md)
## Hosted trust evidence
The hosted mode is designed to be checked from outside:
| Evidence | Where |
|---|---|
| Admission gate refuses unauthorized reads/writes | [Verify it yourself](./verify.md) |
| Stored ledger is ciphertext | `op:'sealed'` |
| App egress is browser-enforced | Content-Security-Policy |
| Deployed render build is signed | `/cert.json` |
| Published source reproduces the deployed hash | `/source.tar.gz` and Test 7 |
The attested server tier is built and independently verifiable, but ordinary hosted production Spaces do not run inside
an enclave today.
Read: [Status](./status.md), [Trust model](./trust-model.md)
## Operational boundaries
Hosted Witbitz can still see metadata:
- room existence
- request timing
- ciphertext sizes
- billing/metering events
- transient identity during gated verification
Hosted Witbitz cannot read the sealed ledger at rest because there is no operator recovery recipient.
## When hosted is not enough
Use on-prem when you need:
- customer-controlled model calls
- customer-owned object storage and records
- customer IdP as the only identity authority
- local backup, retention, and logs
- no Witbitz-operated runtime in the path
Read: [Enterprise and on-prem](./on-prem.md)
---
# Run it in your own boundary — enterprise & on-prem
> **Status: shipped and runnable today (VPC tier).** The entire Witbitz runtime — the app, the agent, the sealed
> ledger, admission — runs inside your own network, off our cloud, on infrastructure you control. One switch (`AIRGAP=1`)
> takes it to zero egress. The one piece still in progress is a *self-hosted model* for the fully air-gapped case
> (below).
Enterprises increasingly won't send their data — or their AI — to a third party. Witbitz answers that literally: you
run the whole thing yourself. And the design keeps this from forking the product — **every external dependency is a
single environment switch that defaults to the hosted behavior when unset.** The build we run for the public and the
build you run in your tenant are the same reproducible image; you just point its seams at your own infrastructure. Same
code, same [verifiable-privacy](https://docs.witbitz.chat/docs/verify.md) properties, your boundary.
## Two tiers
| Tier | Boundary | Model | Who it's for |
|---|---|---|---|
| **VPC** | your cloud tenant; may reach your chosen model API + map services | your own cloud key (OpenAI / Azure) | most enterprise buyers |
| **Air-gapped** | no egress at all | a self-hosted model, in-boundary | defense · government · regulated |
The VPC tier is a complete, operable product now. The air-gapped tier is one switch away — except for the self-hosted
model, which is the remaining substantial piece.
## One command
The runtime ships as a single container plus a Compose stack that wires the whole thing — the app, an S3-compatible
object store for the sealed ledger, the record store, and a reverse proxy with automatic TLS:
```bash
cp space.env.example space.env # your model key + a domain
docker compose up -d --build # the whole stack, off our cloud
curl https://space.your-co.com/space-config
```
There is no per-app server to stand up and no dependency on our infrastructure. The app the browser loads is the same
static, egress-locked, [reproducibly-built](https://docs.witbitz.chat/docs/verify.md) page — now served from your host.
## What each concern maps to
Every seam is a switch. Unset, it's the hosted default; set, it points at infrastructure you own.
| Concern | Runs on | Switch |
|---|---|---|
| **Model** | any OpenAI-compatible endpoint — your cloud key, Azure, or a self-hosted vLLM / Ollama | `LLM_BASE_URL` |
| **Object store** (the sealed ledger) | MinIO or any S3-compatible store | `S3_ENDPOINT` |
| **Records** (Spaces + Bridges) | a local store (single node) or **Postgres** (multi-node HA) | `DATABASE_URL` |
| **Secrets** | mounted files — a Kubernetes Secret or a Vault agent — instead of a cloud secrets manager | `SECRETS_BACKEND=file` |
| **Who may join** | **your own identity provider** — Okta, Entra, or Keycloak — as a trusted OIDC issuer | `SPACE_OIDC_ISSUERS` |
| **Operations** | backup / restore, a retention sweep, bounded log rotation | included in the kit |
| **Zero egress** | refuse every outbound path — see below | `AIRGAP=1` |
### Your identity provider
An email-gated Space admits members who sign in with an issuer you trust. Register your corporate IdP and it's
authoritative — no dependency on any consumer sign-in. It's additive and fail-closed: a registered issuer can't weaken
the built-in ones, the audience is checked (so a token minted for another app can't be replayed), and the Space's own
membership list still decides who's actually in. The IdP only proves *identity*; the Space decides *admission*.
### Operate it
The record store has no silent auto-expiry, so retention is explicit: a sweep reaps Spaces idle past a window you set
(records carry no timestamps — they hold no plaintext — so "idle" is measured from the last write). Backup captures the
sealed ledger, the records, and the secrets in one archive. Logs rotate with a bound and stream to your stack (Loki /
ELK / your SIEM). For high availability, switch the record store to Postgres and run more than one node.
## Air-gap — zero egress
`AIRGAP=1` makes the runtime refuse *every* outbound path, and it does so in ways you can check independently:
1. **The browser.** The served Content-Security-Policy drops every external host. Read the header and it names zero
off-box destinations — the browser itself refuses any external fetch.
2. **The server.** The map / route proxies stop calling out, and the agent's internet-touching tools (web search,
place lookup, flights, external photos, URL fetch) are refused at a single chokepoint — so even a Space configured
with them can't reach out.
Chat, the agent, document reading, PDF generation, charts and diagrams, and IdP admission all keep working. The one
egress this switch can't close is the **model call itself** — point `LLM_BASE_URL` at a self-hosted, in-boundary model
or the turn still leaves the network (the runtime warns loudly at boot if you don't). Standing up an open-weights model
and assessing its quality against a frontier model is the remaining air-gapped-tier work.
## The trust story gets *stronger*, not weaker
Hosted, our signed [`/cert.json`](https://docs.witbitz.chat/docs/verify.md) and reproducible build let a skeptic
confirm the operator — us — is **blind to your data in use**. On-prem there is no third-party operator at all, so the
cert's meaning inverts into **supply-chain proof**: *the image you are running is byte-for-byte the audited source —
nothing was smuggled in.* And air-gapped, "no data leaves" stops being a promise and becomes something you verify on
your own terms:
- **No data leaves** — read the CSP off the served page: zero external hosts. Confirm the proxies refuse.
- **The code is the audited code** — reproduce the build from published source and match the running image's hash;
`op:attest` returns the exact build commit the box is running.
This is where [verifiable privacy](https://docs.witbitz.chat/docs/room-link-auth.md) is at its strongest: both halves
are checkable by you, on hardware you control, without trusting us at all.
## Verify a running deployment
```bash
# the egress allowlist, as a header — in air-gap it names no external host:
curl -sD- https://space.your-co.com/ -o /dev/null | grep -i content-security-policy
# the exact build the box is running:
curl -sXPOST https://space.your-co.com/space -H 'content-type: application/json' -d '{"op":"attest"}'
```
## Talk to us
On-prem and air-gapped deployments are handled as enterprise engagements —
[hello@witbitz.chat](mailto:hello@witbitz.chat?subject=Witbitz%20on-prem).
---
# Air-gap mode
Air-gap mode is the deployment setting where Witbitz refuses every external network path it can control. It is for
environments where "no data leaves" must be a property you can inspect, not a vendor promise.
The short version: set `AIRGAP=1`, point the model to an in-boundary endpoint, and verify the browser and server both
name no outside hosts.
---
## Status
The runtime has a zero-egress switch. The remaining substantial work for a fully useful air-gapped tier is not the
Witbitz runtime itself; it is operating a self-hosted model with the quality the deployment requires, plus local
replacements for any external data tools you want to keep.
Read: [Enterprise and on-prem](./on-prem.md)
## What `AIRGAP=1` changes
| Layer | Behavior |
|---|---|
| Browser | Content-Security-Policy removes external hosts. |
| Server | External map, route, photo, URL, search, and flight paths are refused. |
| Tools | Internet-touching tools fail closed even if a Space configured them. |
| Model | Must be pointed at an in-boundary endpoint; otherwise the turn still leaves the network. |
## What still works
- chat
- agent turns against an in-boundary model
- document reading
- PDF generation
- charts
- diagrams
- shared widget state
- IdP admission against an in-boundary identity provider
- backup, restore, retention, logs
## What is disabled or needs a local replacement
- public web search
- external URL import
- Google Places photos/search
- map tiles, geocode, and routes unless self-hosted
- flights lookup unless backed by an in-boundary service
- remote model APIs
## Verify the browser boundary
Read the CSP header from the served app. In air-gap mode, it should name no external hosts:
```bash
curl -sD- https://space.your-co.com/ -o /dev/null | grep -i content-security-policy
```
## Verify the running build
Ask the local runtime what it is:
```bash
curl -sX POST https://space.your-co.com/space \
-H 'content-type: application/json' \
-d '{"op":"attest"}'
```
Then compare that build identity to the published source and your own deployment process.
## The trust story
Hosted Witbitz asks you to verify the operator's deployment. Air-gapped Witbitz removes the third-party operator from
the path. The check becomes supply-chain oriented:
- Is the image you run the audited source?
- Does the browser policy name no external host?
- Do server-side tools refuse outbound access?
- Is the model endpoint in-boundary?
- Are logs, backups, and retention under your control?
## Related pages
- [Deployment options](./deployment-options.md)
- [Enterprise and on-prem](./on-prem.md)
- [Verify it yourself](./verify.md)
---
# The Bridge — governed collaboration between sovereign Spaces
The Bridge is Witbitz's forward-looking frontier: a **governed relationship between two or more sovereign Spaces**
— *not* another shared room. Each side keeps its own identity, data, policies, and authority; the Bridge defines
what may flow, who participates, and how it's audited. The other side need not even be Witbitz — an external agent
and its humans can join over signed HTTPS. **The protocol is the product.**
> Status: an **early protocol preview**, live at `https://api.witbitz.chat/v1/bridge`. Normative spec:
> [bridge-protocol.md](https://docs.witbitz.chat/bridge-protocol.md); wire contract:
> [bridge.openapi.yaml](https://docs.witbitz.chat/bridge.openapi.yaml).
---
## 1. The shape — three spaces
Two parties each keep a **private prep room** (their people + their agent, sealed under their own key — raw work
never leaves), and they meet in **one shared space** — the Bridge — where approved content is placed and humans can
talk directly. There is **no neutral host**; the parties co-host. A party is *native* (a Witbitz Space) or
*external* (someone else's agent + humans, on their own system) — the two are just implementations of the same
wire contract. It generalises to N parties (the shared space is a first-class Space with its own id).
## 2. The five invariants
The semantics are stated invariants-first; the ops are just mechanism.
1. **Confinement is structural.** The shared space's epoch key opens *only* the shared space. Private rooms are
under different keys — an external or hostile party **can't reach them because it holds no key**, not because a
policy says no.
2. **Attribution by signature.** Every entry is signed by the submitting party's key and verified against its
public key in the membership record — on the ciphertext.
3. **The platform is content-blind.** Bodies are sealed under a key the platform never holds. It checks
membership, capability, epoch, and the hash chain — never the content.
4. **Source-side egress.** Each party approves what it contributes *before* it is sealed into the shared space.
Nothing crosses un-reviewed; the platform never reads content to police it.
5. **Governed, revocable membership + trustless audit.** Every join or revoke mints a fresh epoch key; a joiner
gets no back-history by default; a revoke gives real forward-secrecy. Server-signed **checkpoints** that parties
pin + gossip make equivocation detectable.
## 3. Key epochs
The shared space is a sequence of **epochs**, each with its own key sealed per-member (to each party's box key).
Every **join OR revoke** is an epoch boundary: the new key is sealed only to the new member set. So a **removed**
party can't open the next epoch (forward secrecy), and a **joiner** gets no transcript unless policy explicitly
seals past epochs to it (invited into a conversation, not handed its history).
## 4. Capabilities
Each member carries `caps ⊆ {read, submit, admit, revoke}` → observer (read-only auditor) / party (read+submit) /
admitter (+admit/revoke). SUBMIT/ADMIT/REVOKE are capability-gated.
## 5. The ops (live)
At `https://api.witbitz.chat/v1/bridge`:
| op | what it does | auth |
|----|--------------|------|
| `POST /spaces` | found a shared Space (JOIN, first party) | entry signature |
| `GET /spaces/{s}/members` | the public membership record + sealed key grants | public |
| `PUT /spaces/{s}/members` | admit / revoke — a re-key, an epoch boundary | capability |
| `POST /spaces/{s}/entries` | submit a signed, sealed entry (a load or a post) | entry signature |
| `GET /spaces/{s}/entries` | read the shared thread since a cursor (ciphertext) | public |
| `GET /spaces/{s}/checkpoint` | the latest server-signed head, to pin + gossip | public |
**Deployed auth is thin-but-sound:** SUBMIT is authenticated by the entry's own signature (verified against the
membership pubkey); reads/members/checkpoint are content-blind or public. Per-request `partySignature`
(RFC-9421-style) is the documented hardening (`x-status: proposed`) — the guarantees already hold from the key
boundary + entry signatures.
## 6. Native or external — the interop boundary
The whole point is that the *other side isn't necessarily Witbitz*. A native party and an external party (identity
+ the protocol crypto only, zero Witbitz-Space code) are two implementations of one contract; the interop was
proven end-to-end (a native + an external party co-inhabit one shared space, content-blind). That interop boundary
— *own the governance; let the wire be MCP/A2A/signed-HTTPS* — is why the protocol, not any one app, is the
product.
## 7. Code map
| Concern | Where |
|---|---|
| Membership + capabilities + key epochs | `agent/bridgeMembership.mjs` |
| Signed, hash-chained log + checkpoints | `agent/bridgeLog.mjs` |
| Service + party client (native = external) | `agent/bridgeSpace.mjs` |
| HTTP binding | `agent/bridgeHandler.mjs`; infra `infra/prod-bridge.tf` |
| Normative spec / wire contract | `docs/bridge-protocol.md` · `docs/bridge.openapi.yaml` |
## 8. Status
Live over HTTP + verified end-to-end (found → admit an external party → signed+sealed submit → content-blind read →
checkpoint; governance rejects a stranger/tamper). It is an **early preview** — the frontier of the platform, not
a beta-stable API. The wider vision (org-to-org negotiation, delegated authority across the boundary, a public
transparency log) builds on this.
---
# The Companion — an optional device runtime
> **Status: roadmap, and now optional.** The three things this was meant to add — notifications, durable
> keys, local decryption — the **verifiable web app already does**. The Companion remains a worthwhile
> *convenience* (more reliable background notifications, a Secure-Enclave keystore), but it is no longer a
> pillar of the platform, and nothing on the roadmap depends on it shipping.
A Witbitz app is a **pure static page** — which is exactly what makes its privacy checkable. A static page
famously *can't*, on its own, hold a key safely, notify you while you're away, or decrypt locally. The
Companion was conceived as the one native app that would carry those "un-static" capabilities for every
Witbitz app.
Since then, the web caught up. Here is how each capability is handled **today, with no app** — and what a
Companion would still add on top.
## How it's handled without the Companion
- **Reach-me → installed-PWA push.** Add a Space to the home screen and the platform can reach you with a
**content-free** push (a "something happened" tickle); the device fetches and decrypts locally. Works on
iOS (16.4+, installed), Android, and desktop.
- **Keys → a passkey, not a link.** A **passkey** (WebAuthn, hardware-backed, synced by your provider —
1Password, iCloud Keychain) holds the room secret, so it stops riding the URL. See
[the verified room § lifecycle](https://docs.witbitz.chat/docs/room-link-auth.md).
- **Recovery → user-held.** An encrypted **recovery** — a code, or a passkey-derived secret — that you
control and the server can't read. Lose a link, still get back in. Never an operator key.
- **Local decrypt → in the verifiable client.** The reproducibly-built, egress-locked page decrypts in your
own tab today; [Sealed Spaces](https://docs.witbitz.chat/docs/the-double-blind.md) is the design that makes
the key *never* reach the server at all — structural at-rest blindness, no native app required.
That covers the essentials — and the web version is, if anything, **more** verifiable than an app would be:
a CSP-locked, reproducible static page is something a third party can check; an app-store binary is not.
## What a Companion would still add
A thin native app is worth building as a **convenience**, never a foundation:
- **More reliable notifications** than web push, which is limited on iOS and only fires once installed.
- **A Secure-Enclave keystore** — hardware-isolated keys, a step beyond browser-held ones.
- **One presence across every Space**, and plain app-store legitimacy.
## The honest boundaries
- **It does not close the *in-use* gap.** The **agent** turn runs on server compute, so an agent still
decrypts server-side until the **attested / on-device inference tier** (see
[the double blind](https://docs.witbitz.chat/docs/the-double-blind.md) and the "not protected" column of
[Sealed Spaces](https://docs.witbitz.chat/docs/the-double-blind.md)). A client-side Companion doesn't change
that.
- **It's a convenience, not a requirement.** The platform stands on the **verifiable web** and **Sealed
Spaces**; the Companion is a nicety for people who want it, and can arrive whenever — or never — without
moving the trust story.
## Where it sits
| Capability | Today, no app | A Companion would add |
|---|---|---|
| Key storage | passkey (hardware-backed) + link fragment | Secure-Enclave keystore |
| Recovery | user-held code / passkey-derived secret | same, device-native |
| Notifications | content-free push on an installed PWA | more reliable background delivery |
| Local decrypt | in the verifiable tab; **Sealed Spaces** removes the key from the server | one audited runtime |
| Agent turn (in use) | server-side until the attested tier | unchanged — not what a client app fixes |
See also: [the double blind](https://docs.witbitz.chat/docs/the-double-blind.md) (incl. Sealed Spaces) and
[the verified room § lifecycle](https://docs.witbitz.chat/docs/room-link-auth.md).
---
# The issuer — credit without accounts
> There is a shorter overview of this at **[witbitz.chat/issuer](https://witbitz.chat/issuer)** — the same
> model with less API detail, including the honest-status table of what is live and what is not. This page
> is the one to integrate against.
Witbitz has no user accounts. Access is a link you hold; **credit is a credential you hold.** So who
takes the payment, and how does the platform know what you paid for?
Someone else does. An **issuer** holds the customer, the payment relationship and whatever identity that
requires. Witbitz validates and meters and knows neither. If that shape sounds familiar it is the one
card networks use: your bank knows who you are, the merchant sees only that the payment cleared, and the
scheme in the middle settles between them without holding either side's relationship.
---
## The two sides of a tenant
A tenant sits on one side of the platform, or both:
| role | what it does |
|---|---|
| **`app`** | builds a product on rooms, agents and collections, and **spends** credit |
| **`issuer`** | holds the customer and the payment relationship, and **funds** wallets from its own prefunded balance |
`GET /v1/tenant` reports your `roles`. They are set by an operator — a tenant cannot promote itself to
issuer, because issuing is what turns a balance into spendable value.
A tenant created before roles existed reports `roles: null` and is treated as holding both, so nothing
that worked before the split stopped working.
**Issuers are prefunded.** `POST /v1/wallets/grant` debits the issuer's own balance and returns
`402 insufficient_credit` when it is short. The platform never extends credit, which bounds what an
issuer's failure can cost: users keep credentials already paid for, and nothing outstanding is at risk.
## Epochs — a batch, not a purchase
Every credential names an **epoch**: the batch it was minted in, as an opaque id such as
`ep_47d5a543e80148deb604`.
Credentials used to carry the issuing tenant id instead. Combined with a timestamp that identifies a
*specific purchase*, and the metering ledger used to record which wallet paid for each call — so the two
together reconstructed a path from *who paid* to *what happened in which room*. Both are gone: the
ledger no longer records the wallet at all, and the tenant id became this batch id.
Epoch ids are random, never derived from the tenant. A derived id would be recomputable by anyone
holding the tenant list, which would rebuild the link.
## Publishing a batch
```http
POST /v1/epochs Authorization: Bearer wsk_… (issuer role required)
{ "pub_key": "-----BEGIN PUBLIC KEY-----…", "cents_per_token": 25 }
→ 201 { "epoch": "ep_0158043c8123…", "cents_per_token": 25 }
```
Three rules worth knowing before you integrate:
- **The platform receives only a PUBLIC key.** Private material is refused at write time. Witbitz can
verify what you signed and can never mint on your behalf.
- **A token's value is fixed at publication.** Without `cents_per_token` the redeemer would choose what
a credential is worth.
- **An epoch's key is set once.** Rotation means publishing a *new* epoch. Republishing returns `409` —
otherwise you could restate what already-minted tokens are worth after issuing them.
You may name the epoch yourself if you need to mint tokens before publishing the key.
## Redeeming
```http
POST /v1/wallets/redeem
{ "token": "tok_…", "sig": "