//Magic Link Login

Magic Link Login

Passwordless authentication via one-time email links.

All endpoints require Authorization: Bearer <jwt> unless noted. Token TTL: 15 minutes (non-renewable; request a new link after expiry).

Endpoints

POST /api/auth/magic-link/send

Sends a magic link to the provided email address.

Input: ~~~json { "email": "user@example.com", "application": "myapp" } ~~~

Output: ~~~json { "message": "Magic link sent successfully" } ~~~

GET /api/auth/magic-link/verify?token=:token

Verifies a magic link token and issues a session JWT.

Output: ~~~json { "access_token": "eyJ0...", "user": { "id": "user-uuid-123", "email": "user@example.com" } } ~~~

Tokens are single-use; attempting to verify an already-consumed token returns 400 already_used.

Security Notes

  • Links expire after 15 minutes.
  • Each link is single-use.
  • Rate limited to 5 requests per email per hour.
  • The token is signed with the application's signing key.
  • SDK Examples

    Python

    ~~~python client.magic_link_send("user@example.com", redirect_url="https://app.example.com") resp = client.magic_link_verify("token-from-email") ~~~

    Node.js / TypeScript

    ~~~typescript await client.magicLinkSend('user@example.com', { redirectUrl: 'https://app.example.com' }); const resp = await client.magicLinkVerify('token-from-email'); ~~~

    Go

    ~~~go _, err := client.SendMagicLink(ctx, "user@example.com", nil) resp, err := client.VerifyMagicLink(ctx, "token-from-email") ~~~

    Rust

    ~~~rust client.magic_link_send("user@example.com", Some("https://app.example.com")).await?; let login = client.magic_link_verify("token-from-email").await?; ~~~

    When to Use

  • B2C sign-in where you want to reduce friction
  • B2B sign-in where SSO isn't yet configured but you want better UX than passwords

Error Reference

| Status | Code | Meaning | |--------|------|---------| | 400 | invalid_token | Magic link token is malformed | | 400 | expired_token | Magic link has expired (15 min TTL) | | 400 | already_used | Magic link has already been consumed | | 401 | unauthorized | Token verification failed | | 404 | user_not_found | No account for the email in the link | | 429 | rate_limited | Too many magic link requests for this email |