Skip to content
Docs/Security & access

Account

Security & access

Sign in without passwords, protect accounts with two-factor authentication, require it for your whole team, and see who did what in the audit log.

Signing in

There are no passwords. You sign in at /login with one of:

  • Email: we send a 6-digit code and a magic link. Either one works.
  • Google or GitHub: OAuth 2.0 with PKCE. Only verified email addresses are accepted. A provider identity links to the account with the same verified email, so you can mix methods freely. An identity that already belongs to a different account can't be linked again.

Sessions last 30 days. Connected providers can be disconnected under /app/settings/security (DELETE /me/identities/:provider). Your account keeps working with email sign-in.

Two-factor authentication

UpButler supports authenticator apps (TOTP: RFC 6238, SHA-1, 30-second steps, 6 digits), which covers 1Password, Google Authenticator, Authy, Bitwarden and others. Set it up under /app/settings/security:

  1. Scan the QR code, or enter the secret, in your authenticator app.
  2. Confirm with a current code within 30 minutes.
  3. Save the 10 one-time recovery codes. They're shown once.

From then on, every sign-in method (email code, magic link, Google, GitHub) asks for a code at /login/2fa. Each code works only once, so a code that was intercepted can't be replayed. A recovery code works in place of a code once and is then used up. You can issue a fresh set of recovery codes at any time (the old ones stop working), and turning 2FA off needs a current code.

  • A pending two-factor sign-in expires after 10 minutes or 5 wrong codes.
  • Code attempts are rate-limited (10 per 15 minutes per account, 30 per 15 minutes per IP).
  • Authenticator secrets are stored encrypted. Recovery codes are stored only as hashes.

Requiring 2FA for a workspace

The workspace owner can require two-factor for every member in /app/settings/security (PATCH /workspace/security with {"require2fa": true}, dashboard session only). The owner must have 2FA on first. The response reports how many members don't have it yet.

  • Members without 2FA are sent to the security settings when they open the dashboard. Their session can't act on the workspace until they've enrolled.
  • Members of a workspace that requires 2FA can't turn it off. An owner has to lift the requirement, or they leave the workspace.
  • The members list shows who has 2FA.
  • API keys are not affected. They're separate credentials, governed by their scopes. See below.

Sessions

/app/settings/security lists your active sessions with device (“Chrome on macOS”), IP, sign-in method, and when each was created and last seen. Sign out a single session, or every session except the current one. These are dashboard-only operations (GET /me/security, DELETE /me/sessions/:id, POST /me/sessions/revoke-others).

API keys

  • Keys (ub_live_…) belong to a workspace and carry the read and/or write scope. Only a hash is stored. The secret is shown once.
  • Give each agent or integration its own key with an agent name, so its actions are attributed in incident timelines and the audit log. Set expiresInDays for temporary access.
  • Revoke a key with DELETE /keys/:id. It stops working immediately.
  • Cookie-authenticated (dashboard) writes must come from the same origin. API keys are meant for servers and agents, so never ship a write-scoped key to a browser.

Audit log

Security- and admin-relevant actions are recorded with who, what, target, IP and time, and kept for 400 days. A workspace's log contains its own actions plus the account-level events (sign-ins, 2FA changes) of its current members. Owners and admins see it at /app/settings/audit. Over the API, it needs a key with the write scope.

Events are recorded on every plan. The viewer and GET /audit show the full 400 days on the Business plan. On other plans they show the last 7 days, and the response carries limitedToDays: 7 and a hint. Older entries are kept and come into view after an upgrade.

curl "https://upbutler.com/api/v1/audit?action=auth&limit=2" \
  -H "Authorization: Bearer $UPBUTLER_API_KEY"
200 OK
{
  "data": [
    {
      "id": "aud_0n3qd2k8m1p4r7t0w3y",
      "action": "auth.sign_in",
      "actor": { "type": "user", "id": "usr_0n3q8jy7w2e4r6t8y0u", "name": "Dana Weber" },
      "target": null,
      "meta": { "method": "github", "twoFactor": "totp" },
      "ip": "203.0.113.24",
      "at": "2026-10-09T07:58:12.004Z",
      "scope": "account"
    },
    {
      "id": "aud_0n3qd1x2c5v8b1n4m7q",
      "action": "auth.2fa_failed",
      "actor": { "type": "user", "id": "usr_0n3q8k01d5f7h9j1k3l", "name": "Sam Lee" },
      "target": null,
      "meta": { "method": "email_code" },
      "ip": "198.51.100.7",
      "at": "2026-10-09T07:41:50.873Z",
      "scope": "account"
    }
  ],
  "next": "aud_0n3qd1x2c5v8b1n4m7q"
}

Filters: action (an exact action such as api_key.created, or a prefix such as auth or member.), actorId, from/to, and limit (max 200). Newest first. Page with before=<next>. scope tells workspace actions from account events.

ActionRecorded when
auth.sign_inSuccessful sign-in (meta: method, and twoFactor = totp | recovery when used)
auth.sign_outSigned out
auth.2fa_failedWrong two-factor code at sign-in
auth.session_revoked / auth.sessions_revokedOne session, or all other sessions, signed out
user.2fa_enabled / user.2fa_disabledTwo-factor turned on or off
user.recovery_codes_regeneratedNew recovery codes issued
user.identity_linked / user.identity_unlinkedGoogle or GitHub sign-in connected or disconnected
workspace.2fa_required / workspace.2fa_optionalOwner changed the two-factor requirement
workspace.renamedWorkspace renamed
member.invited / member.invite_revoked / member.removedMembership changes
api_key.created / api_key.revokedAPI key lifecycle
billing.checkout_started / billing.portal_openedBilling actions
monitor.deleted / page.deletedDeletions
page.domain_changedA status page custom domain was set, changed or removed