Skip to main content
Version: v0.0.47

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_orgs row 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.
  • Bulk revoke / bulk resend (v0.0.47+) — checkbox column on pending rows (accepted/revoked/expired rows are never re-actionable). Header select-all toggles every pending row; the action bar appears on first selection. Revoke runs as a single SQL UPDATE and reports partial counts ("Revoked 3 of 5 (2 already accepted)"); Resend categorizes each row into sent / skipped (accepted, revoked, expired, send_failed) and re-fires the invite email — per-row failures don't abort the batch. Both endpoints are admin-only with a defensive 200-row cap. Audit-log writes summarize the batch outcome.
  • 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.

KPIWhat it counts
LLM callsChat + agent calls in the period (split by source)
dbt runsSuccessful + failed dbt runs
DatasetsActive datasets in the org
DashboardsActive dashboards in the org
MembersActive users + open-invite count
StorageBytes 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_url field 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
  1. Add domain. Enter the hostname. Honeyframe rejects its own apex + reserved hostnames; gives you a verification token.
  2. Set DNS TXT. Add the TXT record on your DNS provider. Honeyframe polls; once the record is visible the row flips verifying → active.
  3. Cert + nginx. On active, Honeyframe renders an nginx vhost, reloads, and runs certbot --nginx for HTTP-01.
  4. Live. The host_org middleware resolves the host to the same org_id it would for a <slug>.app.honeyframe.io hit, 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.

ProbeWhat it checks
dbSELECT 1 round-trip on the platform DB
dbtdbt --version subprocess (5-second timeout)
diskshutil.disk_usage(DATA_DIR) against 15% (warn) / 5% (error) free thresholds
schedulerhoneyframe.app_config heartbeat freshness (5 min warn, 30 min error)
llmBoolean presence of OpenAI / Anthropic / local provider keys (never echoes the keys)
smtpBoolean 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-Signature header as sha256=<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_TENANT accepts a specific tenant GUID, common (any account), organizations, or consumers.
  • 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 carry org_id.
  • Bait-endpoint defense — a discovery doc whose issuer field 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 signxml for 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..., plain email, 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.