Workspace
The Workspace nav section (sitting above Administration in the app shell) holds the pages a tenant admin needs to operate their org without filing a support ticket. Every page on this surface is admin-gated and scoped to the caller's org — there is no cross-tenant view here.
Pages introduced in v0.0.44 (Members / Usage / AI Keys / Custom Domains / Health) plus four added in v0.0.45 (SMTP / Backups / Webhooks / SSO providers).
Members
/members — Invite teammates by email. Recipients receive a link, land on a public /accept-invite page, set their own password, and join the org with the assigned role.
- Existing accounts — invites for an email that already has a Honeyframe account add a
user_orgsrow instead of creating a duplicate user. The recipient signs in with their existing password and sees the new org in their account picker. - Token TTL — 7 days. Past expiry the link 410s.
- Revoke — open the invite row and click Revoke. Effective immediately.
- Dedup — the platform refuses a duplicate open invite for the same email; revoke the old one first.
- Storage — sha256-hashed at rest in
honeyframe.invites. The bare token is never stored.
Usage
/usage — Read-only KPI roll-up over your org. Period switcher: Today / 7d / 30d / All.
| KPI | What it counts |
|---|---|
| LLM calls | Chat + agent calls in the period (split by source) |
| dbt runs | Successful + failed dbt runs |
| Datasets | Active datasets in the org |
| Dashboards | Active dashboards in the org |
| Members | Active users + open-invite count |
| Storage | Bytes on disk under DATA_DIR/orgs/<org_id>/ + file count |
Storage is walked from disk and cached server-side for 5 minutes — click Refresh storage after a large upload to drop the cache and re-walk.
There is no write surface on this page. Quota policy decisions (e.g. limits, alerts) belong to the operator, not this view.
AI Keys
/ai-keys — Bring your own LLM keys. Paste an OpenAI / Anthropic / Ollama key and that org's chat / SQL / agent traffic bills against your account instead of the platform default.
- Provider list — OpenAI, Anthropic, Ollama (with a
base_urlfield for self-hosted Ollama). - Model override — optional. If unset the platform falls through to its own default per provider.
- Fallback — when a provider key is unset for an org, that org continues to use the platform default. Existing tenants on a v0.0.43+ deploy see no behavior change.
The plaintext key never appears on screen after save:
- The form input is
type=password. - The list shows only
••••<last_4>. - The server returns
{provider, masked_key, last_4, model_override}. Plaintext is never read back, even by the page that wrote it. - Remove is gated behind a confirm dialog. Removing reverts that provider to the platform default.
Encryption: AES-GCM over the plaintext key. The encryption key is derived from the existing platform JWT secret (no new operator env required). Decrypt failure on a key (e.g. JWT secret rotated) falls through to the platform default — the LLM stays available even with half-rotated keys.
Custom Domains
/custom-domains — Map your own domain (data.acme.com) to your Honeyframe Space.
State machine:
pending → verifying → active
↘ failed ↘ removed
- Add domain. Enter the hostname. Honeyframe rejects its own apex + reserved hostnames; gives you a verification token.
- Set DNS TXT. Add the TXT record on your DNS provider. Honeyframe polls; once the record is visible the row flips
verifying → active. - Cert + nginx. On
active, Honeyframe renders an nginx vhost, reloads, and runscertbot --nginxfor HTTP-01. - Live. The
host_orgmiddleware resolves the host to the sameorg_idit would for a<slug>.app.honeyframe.iohit, so auth / license / branding / RBAC work identically.
Failure at any phase flips status to failed with last_error captured. Click Retry to re-run from the failure point. Removing a domain triggers nginx vhost teardown + certbot revoke; the row stays for audit with status='removed'.
Health
/health — Six-probe operator dashboard. Polls every 30 seconds and rolls up the worst probe status into an overall badge.
| Probe | What it checks |
|---|---|
| db | SELECT 1 round-trip on the platform DB |
| dbt | dbt --version subprocess (5-second timeout) |
| disk | shutil.disk_usage(DATA_DIR) against 15% (warn) / 5% (error) free thresholds |
| scheduler | honeyframe.app_config heartbeat freshness (5 min warn, 30 min error) |
| llm | Boolean presence of OpenAI / Anthropic / local provider keys (never echoes the keys) |
| smtp | Boolean presence of SMTP password (no test-send) |
Status colors: emerald = ok, amber = warn, red = error. Each card shows the probe message, latency in ms, and a key/value meta panel (provider booleans, free-disk %, heartbeat age, etc.). Click Refresh to force an out-of-cycle fetch.
Crash isolation: a single probe raising never 500s the endpoint. The aggregator catches and surfaces the failure in that component's status, so the rest of the dashboard keeps rendering.
Distinct from /api/health (the 3-line liveness check used by the load balancer + license-gate allowlist) — the one at /api/system/health is admin-only, richer, and never echoes secret values.
24h trend chart
v0.0.45 added a persisted timeline below the live cards: a horizontal status band per component with a range selector (1h / 6h / 24h / 3d / 7d). Each segment renders with the probe status color + a tooltip carrying the probe message + timestamp.
Persistence is dedupe-aware: the platform inserts a snapshot when overall_status flips OR every 5 minutes for stable systems. Steady-state cost ~12 rows/hour — small enough to keep indefinitely. The history endpoint is supplementary — never blanks the page if missing or malformed (so an upgrade mid-flight where the history table doesn't exist yet doesn't break the live view).
SMTP
/smtp (v0.0.45) — Plug in your own SMTP host so invite + password-reset emails arrive from <team>@<your-domain> instead of the platform default.
Per-tenant relay improves deliverability (recipient SPF/DKIM checks pass against your DNS), branding (subject + From line carry your domain), and capacity (no shared-relay quota). The password is encrypted at rest with the same key derivation as AI Keys — operators rotate once, both surfaces follow.
- First save requires the password.
- Subsequent edits with the password field blank preserve the existing encrypted blob.
- GET never returns the blob or the plaintext — only
password_set: true|false. - Test send dispatches a one-off message to verify host/auth before going live; the SMTP error is surfaced verbatim (502 + detail) so misconfigurations are debuggable from the UI.
Forgot-password resolves the user's primary org_id from user_orgs so reset emails route through the tenant relay even on the unauthenticated request.
Backups
/backups (v0.0.45) — Tenant-driven logical backup of your org's data. Snapshot before a risky migration / mass delete, and restore without filing a ticket.
Coverage. Every honeyframe.* table that carries an org_id column, plus per-tenant t<pid>_uploads.* tables. Schema discovery walks INFORMATION_SCHEMA on every backup so future migrations are auto-included.
Storage. {DATA_DIR}/_backups/<org_slug>/<YYYYMMDD-HHMMSS>.tar.gz. Auto-prune keeps 20 backups per org.
Restore. Click Restore on the row, type RESTORE <slug> verbatim in the prompt, confirm. Single transaction: the org's existing rows are TRUNCATEd, CSV is re-INSERTed, per-tenant uploads schemas are dropped + recreated. The backend gates the confirm phrase server-side — a determined attacker bypassing the prompt still gets 400'd.
Webhooks
/webhooks (v0.0.45) — Push platform events to your own systems (Slack, n8n, PagerDuty, custom services) on a real-time basis.
- Payload format — JSON body, HMAC-signed in the
X-Honeyframe-Signatureheader assha256=<hex>— the same shape Stripe and GitHub use, so existing receiver scaffolds verify without modification. - Retry — 4xx responses are terminal (the receiver said no). 5xx and transport errors retry on
1m / 5m / 15m / 1h, then drop. The delivery log captures every attempt: status code, response excerpt (capped at 512 chars),next_retry_at. - Subscriptions — leave the events array empty to receive every event (Stripe convention), or check specific event types from the catalog.
- Test send — a one-shot synchronous dispatch button on each endpoint row. Returns the receiver's response code + excerpt verbatim so misconfigurations are debuggable from the UI.
The signing secret is echoed once on create / rotation. Subsequent GETs return only a 6-character preview tail. Lose the secret → rotate it.
SSO providers
In addition to Google OAuth (already supported), v0.0.45 adds three admin-configured per-org SSO paths. All four share identical conventions: auto-provision new users with role=viewer, bypass must_reset_password (SSO is the identity-of-record), and emit <provider>_login audit rows.
Microsoft (Azure AD / Entra ID)
/microsoft-providers. Operator sets MICROSOFT_CLIENT_ID + MICROSOFT_TENANT env-side; admin enables the button.
MICROSOFT_TENANTaccepts a specific tenant GUID,common(any account),organizations, orconsumers.- Multi-tenant pseudo-tenants (
common,organizations) skip the issuer check because the actual issuer carries the user's tenant id, not the literal pseudo string. - Issuer check accepts both v1 (
sts.windows.net/<tid>/) and v2 (login.microsoftonline.com/<tid>/v2.0) formats. - Email resolution walks
email > preferred_username > upn, lowercased.
Generic OIDC
/oidc-providers. Paste an issuer URL + client_id; the platform follows the OIDC discovery spec. Works with Okta, Auth0, Keycloak, Ping, Cloudflare Access, anything OIDC-compliant.
- Per-
(org_id, slug)addressing with a globally-unique slug — the public sign-in URL doesn't carryorg_id. - Bait-endpoint defense — a discovery doc whose
issuerfield doesn't match what was configured fails fast. The platform never silently trusts whatever URL serves the discovery JSON. - Discovery + JWKS cached 24h; cache-bust + retry on unknown kid handles legitimate key rotations.
- Per-slug state stash (
sessionStorage["oidc_state_<slug>"]) so a state minted for one provider cannot satisfy another's flow.
SAML 2.0
/saml-providers. For ADFS / Azure AD SAML / Okta SAML / OneLogin / the long tail of old-line enterprise IdPs.
- Uses
signxmlfor signature verification — canonicalization + XML Signature Wrapping (XSW) defense. - Cert paste handles both full PEM blocks AND bare base64 — the most common operator-paste mistake (IdP admin UIs often show just the body).
- Email resolution walks the common attribute aliases (xmlsoap claim,
urn:oid:0.9.2342..., plainemail, NameID fallback when email-shaped). - Fragment redirect at
/saml-callback/<slug>#token=<jwt>keeps the JWT off the server hop.
Domain allowlist
Every SSO row carries an optional allowed_email_domains field. Empty allowlist means "any verified account from this IdP" — suits multi-tenant deployments where org membership is gated by invites instead. A populated allowlist is enforced before the JWT mints; a violation returns 403.
Replay welcome tour
The user-avatar menu has a Show welcome tour again item that resets your onboarding state and reloads the app. The 5-step first-login wizard re-mounts on the next route, walking you through Welcome dashboard → Connectors → Members → AI Keys → Usage. Reset is self-only — there is no admin surface to reset another user's tour.