Simulix

API

Authentication

Simulix supports two auth methods: session cookies for the dashboard + admin surface, and Bearer API keys for the /v1 API. Pick the right one for the integration.

Two auth methods

Use sessions when a real human is interacting with the dashboard. Use Bearer keys when a server, cron, or worker is calling the API on behalf of an organization. Both produce the same downstream identity (organization_id, plan_tier, scopes) — your code doesn't need to know which path was taken.

Sign in with magic link

Magic-link sign-in is passwordless. POST an email; the user gets a one-time link that expires in 15 minutes. The same endpoint covers signup AND login — the response is 202 sent: true regardless of whether the email already exists, so we never leak whether someone has an account.

curl -X POST https://api.simulix.com/v1/auth/magic-link/request \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "founder@acme.com",
    "name": "Founder Name",
    "organization_name": "Acme Inc"
  }'

In dev mode (SIMULIX_DEBUG=1) the response body also includes data.magic_link for smoke testing. Production never echoes the link via the API — the user receives it via email and clicks through.

If the upstream email provider rejects the message or is unreachable, the endpoint returns 503 email_delivery_failed with a correlation_id in meta. The just- issued magic-link token is rolled back from cache before the 503 escapes, so a retry mints a fresh token without conflict. Operators can trace the failure via the correlation_id — see docs/ops/resend-setup.md.

Verifying the link

The user clicks the email link, lands on /auth/callback?token=…, and the frontend POSTs the token to verify. The server atomically consumes the token (so a replay returns 401 magic_link_invalid), updates last_login_at, and sets the simulix_session cookie.

curl -i -c cookies.txt -X POST https://api.simulix.com/v1/auth/magic-link/verify \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"token":"<token-from-the-link>"}'

Sessions

  • Cookie name: simulix_session — HttpOnly, SameSite=Lax, Secure (in non-dev), Path=/, Max-Age=604800 (7 days).
  • Storage: Redis only. Sessions never persist to the database — logout means deleting the Redis key.
  • Logout: POST /v1/auth/logout deletes the session from Redis and clears the cookie. Idempotent — calling it without a cookie still returns 204.
  • /v1/auth/me returns the current user + organization — handy for dashboard bootstrap. Accepts EITHER a session cookie OR a Bearer API key (user is null for Bearer).

Session vs Bearer

DimensionSession cookieBearer API key
Use caseDashboard, /admin, web UIServer-to-server API calls, SDKs
CredentialHttpOnly cookie (simulix_session)Authorization: Bearer sk_*
Lifetime7 days (sliding refresh on activity)Until revoked or expired
RotationRe-issue via magic linkPOST /v1/api-keys/{id}/rotate
IdentifiesUser + OrganizationOrganization (no specific user)
ScopesAll 6 (founder = admin for MVP)Per-key scope set

Stripe-style signup (planned)

Day-11 wires Stripe webhooks into the same magic-link flow: when a paid customer completes Checkout, Stripe's checkout.session.completed webhook calls request_magic_link() on the buyer's email. The buyer gets a sign-in email with no password to set, and the org auto-upgrades from sandbox growth on first verify. New code paths needed: zero — the magic-link primitives from this PR carry it.

See API Keys for the API-side credential model.

Endpoint reference

MethodPathStatusSummary
POST/v1/auth/magic-link/request202 / 503Send a magic link (signup OR login). 503 email_delivery_failed when the upstream provider rejects.
POST/v1/auth/magic-link/verify200 / 401Consume token, set session cookie.
POST/v1/auth/logout204Clear cookie + delete Redis session.
GET/v1/auth/me200 / 401Current user + organization.