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/logoutdeletes the session from Redis and clears the cookie. Idempotent — calling it without a cookie still returns204. - /v1/auth/me returns the current user + organization — handy for dashboard bootstrap. Accepts EITHER a session cookie OR a Bearer API key (
userisnullfor Bearer).
Session vs Bearer
| Dimension | Session cookie | Bearer API key |
|---|---|---|
| Use case | Dashboard, /admin, web UI | Server-to-server API calls, SDKs |
| Credential | HttpOnly cookie (simulix_session) | Authorization: Bearer sk_* |
| Lifetime | 7 days (sliding refresh on activity) | Until revoked or expired |
| Rotation | Re-issue via magic link | POST /v1/api-keys/{id}/rotate |
| Identifies | User + Organization | Organization (no specific user) |
| Scopes | All 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
| Method | Path | Status | Summary |
|---|---|---|---|
| POST | /v1/auth/magic-link/request | 202 / 503 | Send a magic link (signup OR login). 503 email_delivery_failed when the upstream provider rejects. |
| POST | /v1/auth/magic-link/verify | 200 / 401 | Consume token, set session cookie. |
| POST | /v1/auth/logout | 204 | Clear cookie + delete Redis session. |
| GET | /v1/auth/me | 200 / 401 | Current user + organization. |