---
title: "Consume Magic Link"
method: POST
path: "/auth/magic-link/consume"
tags: ["Auth"]
---

# Consume Magic Link

`POST /auth/magic-link/consume`

## Request

- Base URL: `http://localhost:8000`
- URL: `http://localhost:8000/auth/magic-link/consume`
- Auth: none declared

Exchange a magic-link token for a session JWT.

Magic-link replaces the *password* factor, not the MFA challenge
(see ``docs/auth/MFA.md`` §"Magic-link interaction"). The
response mirrors ``/login``'s discriminated union:

- ``kind: "session"`` — user has no active MFA. A full session JWT
  with ``mfa_satisfied=false`` is returned. Extranet routes still
  bounce these tokens off ``get_admin_user`` /
  ``get_property_admin_user`` once enforcement is on; guest users
  sail through.
- ``kind: "mfa_required"`` — user has at least one active MFA
  method. A 5-minute ``mfa_challenge_token`` is returned and must
  be exchanged at ``/auth/mfa/verify-login`` for a real session.
  This is the path that prevents a magic-linked admin from being
  redirected to ``/account/security?reason=mfa_required`` despite
  already having MFA configured.

## Headers

- `API-Version` '1'

## Request body

- MagicLinkConsumeRequest
  - `token` string, required

## Response `200`

Successful Response

- union
  - LoginSuccessResponse: Returned by /login when the user has no active MFA. Backward-compat with the pre-MFA shape — extra ``kind`` field is the only diff.
    - `id` integer, required
    - `username` string, email, required
    - `firstname` string, nullable
    - `lastname` string, nullable
    - `displayname` string, nullable
    - `phone` string, nullable
    - `register_date` string, date-time, required
    - `roles` unknown[]
      - unknown
    - `profile_pic` string, required
    - `access_token` string, nullable
    - `pending_email` string, nullable
    - `mfa_satisfied` boolean
    - `mfa_enrolled` boolean
    - `mfa_recovery` boolean
    - `mfa_access_required` boolean
    - `kind` 'session'
    - `return_url` string, nullable
  - MfaChallengeResponse: Returned by /login when the user has at least one active MFA method. The challenge token must be exchanged at /auth/mfa/verify-login. ``mfa_methods`` is computed from the user's actual active method types (plus the implicit ``recovery_code`` fallback) so the client only renders branches the user can actually satisfy. ``preferred_hint`` is the most recently used primary factor (``totp`` / ``webauthn``) when both are enrolled — soft UI ordering only; ``None`` when neither has been used yet. ``extranet_handoff`` is true when the account holds ``admin`` or any ``sa_*`` role. NZ guest hosts use this to send operators to the AU-canonical login before starting an MFA ceremony (passkeys are RP-bound to ``totalparks.com.au``). A boolean avoids leaking the full role list on the unauthenticated challenge response.
    - `kind` 'mfa_required'
    - `mfa_challenge_token` string, required
    - `mfa_methods` string[], required
    - `preferred_hint` 'totp' | 'webauthn', nullable
    - `extranet_handoff` boolean
    - `return_url` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/totalparks/apis/total-parks-public-api.md) · [All operations](https://skmtc.dev/totalparks/apis/total-parks-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/totalparks/apis/total-parks-public-api/revisions/dcb4c822e2c6?raw)
