Skip to content
Logo

Authentication

Two auth schemes, mutually exclusive on a single request:

SchemeHeaderUse case
API keyApi-Key: seismic_prod_…Server-to-server. Org-scoped (one key, one org). No login or refresh.
Access tokenAuthorization: Bearer <access-token>Browsers logged into orchestration.seismic.systems. Issued by the email/password/MFA flow below.

Sending both → 400 BothCredsProvided. The SDK handles either via separate constructor options (apiKey vs tokenStore). See SeismicOrchestrationClient.

For server-to-server integrations, mint an API key in the dashboard (or via POST /auth/api-keys). The rest of this page describes the access-token flow used by the dashboard.


The dashboard authenticates with Authorization: Bearer <access_token>. Access tokens are short-lived (minutes). Refresh tokens are long-lived (days) and live inside the /auth/refresh flow.

Token lifecycle

signup ─┐
        ├─► login ──► tokens  ──────────────────► (use access until it 401s)
        │     │                                            │
        │     └──► mfa_required ──► mfaVerify ──► tokens   │
        │                                                  ▼
        │                                               refresh ── tokens
        │                                                  │
        └────────────────────────────────────────────── logout
  • Access token: minutes-long. Sent on every request. Stateless JWT, verified against JWT_ACCESS_SECRET.
  • Refresh token: days-long. Opaque. Stored server-side in refresh_tokens, one row per session. Rotated on every refresh call. The previous value is invalidated.
  • MFA challenge id: seconds-long. Issued by login when MFA is active. Redeemed by mfaVerify for a real token pair.

Signup

import {  } from "./client";
 
const {  } = await ..({
  : "alice@example.com",
  : "hunter22!hunter22",
  : "Alice",
  : "Anderson",
});
StatusMeaning
200{ user_id }: account created. No tokens yet. Call login.
400Password shorter than 10 chars, invalid email, or missing name.
409Email already in use.

Login (password-only)

import {  } from "./client";
 
const  = await ..("alice@example.com", "hunter22!hunter22");
 
switch (.) {
  case "tokens":
    // Straight to a token pair: MFA not enrolled or not required.
    .("access", .);
    break;
 
  case "mfa_required":
    // Punt the challenge_id to your MFA screen.
    (`/login/mfa?challenge=${.}`);
    break;
}
 
function (: string) {}

The return type is a Zod-parsed discriminated union, so TypeScript narrows both arms for you.

MFA

Two flows, both using TOTP:

  1. Enroll (signed-in user, one-time): mfaEnroll → scan QR → mfaActivate(code).
  2. Challenge (every login after activation): login returns mfa_required → mfaVerify(challenge_id, code) → token pair.

Enrollment

import {  } from "./client";
 
// Returns an otpauth:// URI + the raw secret for display.
const  = await ...();
(.);
 
// Prompt the user to type the 6-digit code from their authenticator app.
const  = await ();
await ...();
// Returns a fresh TokenPair (mfa_passed=true); client persists it
// automatically, so this session continues without a re-login.
 
declare function (: string): void;
declare function (): <string>;

Challenge

import { ,  } from "./client";
 
const  = await ..(, );
if (. === "mfa_required") {
  const  = await ();
  const  = await ...(., );
  .();
}

SMS-based 2FA is not supported. For sandbox-only ops escape hatches, set ALLOW_MFA_BYPASS=true in the server env.

Refresh

The SDK catches 401s on protected paths and refreshes automatically. Call directly only when managing tokens in a background job or external process:

import {  } from "./client";
 
const  = await ..();
// pair.access_token, pair.refresh_token (new, old one is dead)

Logout

Revokes the refresh token server-side. Access tokens remain valid until expiry (stateless JWTs aren't revocable without a denylist). Keep access TTLs short.

import { ,  } from "./client";
 
const  = .();
if () await ..();
.();

Inspecting the current user

GET /auth/me is the single canonical whoami: full profile, memberships, MFA state. Works equally for Api-Key: callers (returns the user who minted the key) and dashboard-session callers (returns the logged-in user).

import {  } from "./client";
 
const  = await ..();
//    ^ id, email, mfa_required, mfa_active, mfa_passed, memberships[], created_at