> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agent-auth`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Headless email API for magic link and OTP

This guide shows you how to implement magic link and OTP authentication using Scalekit's headless APIs. You send either a one-time passcode (OTP) or a magic link to the user's email and then verify their identity. Magic link and OTP offer two email-based authentication methods—clickable links or one-time passcodes—so users can sign in without passwords. You control the UI and user flows, while Scalekit provides the backend authentication infrastructure.

<details>
<summary><IconLucidePlay style="display: inline; width: 1rem; height: 1rem; vertical-align: middle; margin-right: 0.5rem;" /> See the integration in action</summary>

<VideoPlayer link="https://youtu.be/8e4ZH-Aemg4" />

</details>

<details>
<summary><IconTdesignSequence style="display: inline; width: 1rem; height: 1rem; vertical-align: middle; margin-right: 0.5rem;" /> Review the authentication sequence</summary>

Coming soon

</details>

<FoldCard
  title="Build with a coding agent"
  iconKey="build-with-ai"
  iconPosition="right"
  href="/dev-kit/build-with-ai/full-stack-auth/"
  cta="Build with AI"
  showCta={false}
  clickable={false}
>
  ```bash title="Claude REPL" showLineNumbers=false frame="none"
    /plugin marketplace add scalekit-inc/claude-code-authstack
    ```
    ```bash title="Claude REPL" showLineNumbers=false frame="none"
    /plugin install full-stack-auth@scalekit-auth-stack
    ```
   ```bash title="Terminal" showLineNumbers=false frame="none"
    copilot plugin marketplace add scalekit-inc/github-copilot-authstack
    ```
    ```bash title="Terminal" showLineNumbers=false frame="none"
    copilot plugin install full-stack-auth@scalekit-auth-stack
    ```
   ```bash title="Terminal" showLineNumbers=false frame="none"
    npx skills add scalekit-inc/skills --skill implementing-scalekit-fsa
    ```
   [Continue building with AI →](/dev-kit/build-with-ai/full-stack-auth/)
</FoldCard>

-------

1. ## Set up Scalekit

   Install the Scalekit SDK to your project.

   <InstallSDK />

   Your application is responsible for verifying users and initiating sessions, while Scalekit securely manages authentication tokens to ensure the verification process is completed successfully

2. ## Configure magic link and OTP settings

   In the Scalekit dashboard, enable magic link and OTP and choose your login method.

   Optional security settings:
   - **Enforce same-browser origin**: Users must complete magic-link auth in the same browser they started in.
   - **Issue new credentials on resend**: Each resend generates a fresh code or link and invalidates the previous one.

    ![](@/assets/docs/fsa/auth-methods/1.png)

3. ## Send verification email

    The first step in the magic link and OTP flow is to send a verification email to the user's email address. This email contains either a **one-time passcode (OTP), a magic link, or both** based on your selection in the Scalekit dashboard.

    Follow these steps to implement the verification email flow:

1. Create a form to collect the user's email address
2. Call the passwordless API (magic link and OTP) when the form is submitted
3. Handle the response to provide feedback to the user

    ```http showLineNumbers=false title="API endpoint"
    POST /api/v1/passwordless/email/send
    ```

    **Example implementation**

    ```sh {11} "/api/v1/passwordless/email/send" title="Send a verification code to user's email" { "Only if Link or Link+OTP as authentication method": 9} collapse={17-22}
      curl -L '<SCALEKIT_ENVIRONMENT_URL>/api/v1/passwordless/email/send' \
      -H 'Content-Type: application/json' \
      -H 'Authorization: Bearer eyJh..' \
      --data-raw '{
          "email": "john.doe@example.com",
          "expires_in": 300,
          "state": "jAy-state1-gM4fdZ...2nqm6Q",
          "template": "SIGNIN",

          "magiclink_auth_uri": "https://yourapp.com/passwordless/verify",
          "template_variables": {
            "custom_variable_key": "custom_variable_value"
          }
      }'

    # Response
    # {
    # "auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"
    # "expires_at": "1748696575"
    # "expires_in": 100
    # "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
    # }

    ```
    <details>
      <summary>Request parameters</summary>

      | Parameter  | Required | Description |
      |------------|----------|-------------|
      | `email` | Yes | Recipient's email address <Badge variant="note" text="string" /> |
      | `expires_in` | No | Code expiration time in seconds (default: 300) <Badge variant="note" text="number" /> |
      | `state` | No | OIDC state parameter for request validation <Badge variant="note" text="string" /> |
      | `template` | No | Email template to use (`SIGNIN` or `SIGNUP`) <Badge variant="note" text="string" /> |
      | `magiclink_auth_uri` | No | Magic Link URI that will be sent to your user to complete the authentication flow. If the URL is of the format `https://yourapp.com/passwordless/verify`, the magic link sent to your user via email will be `https://yourapp.com/passwordless/verify?link_token=<link_token>`. Required if you selected Link or Link + OTP as your authentication method.<Badge variant="note" text="string" /> |
      | `template_variables` | No | Pass variables to be used in the email template sent to the user. You may include up to 30 key-value pairs to reference in the email template. <Badge variant="note" text="object" /> |
    </details>

    <details>
      <summary>Response parameters</summary>

      | Parameters | Description |
      |-----------|-------------|
      | `auth_request_id` | A unique identifier for the authentication request that can be used to verify the code <Badge variant="note" text="string" /> |
      | `expires_at` | Unix timestamp indicating when the verification code will expire <Badge variant="note" text="string" /> |
      | `expires_in` | The time in seconds after which the verification code will expire. Default is 100 seconds <Badge variant="note" text="number" /> |
      | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP` <Badge variant="note" text="string" /> |
    </details>

   ```js wrap
    const options = {
      template: "SIGNIN",
      state: "jAy-state1-...2nqm6Q",
      expiresIn: 300,
      // Required if you selected Link or Link+OTP as your authentication method
      magiclinkAuthUri: "https://yourapp.com/passwordless/verify",
        templateVariables: {
            employeeID: "EMP523",
            teamName: "Alpha Team",
        },
    };

    const sendResponse = await scalekit.passwordless
      .sendPasswordlessEmail(
        "<john.doe@example.com>",
        options
    );

    // sendResponse = {
    //   authRequestId: string,
    //   expiresAt: number,    // seconds since epoch
    //   expiresIn: number,    // seconds
    //   passwordlessType: string  // "OTP" | "LINK" | "LINK_OTP"
    // }

    ```

     <details>
        <summary>Request parameters</summary>
      | Parameter | Required | Description |
      |-----------|----------|-------------|
      | `email` | Yes | The email address to send the magic link or OTP verification code to <Badge variant="note" text="string" /> |
      | `template` | No | The template type (`SIGNIN`/`SIGNUP`) <Badge variant="note" text="string" /> |
      | `state` | No | Optional state parameter to maintain state between request and callback <Badge variant="note" text="string" /> |
      | `expiresIn` | No | Optional expiration time in seconds (default: 300) <Badge variant="note" text="number" /> |
      | `magiclinkAuthUri` | No | Magic Link URI that will be sent to your user to complete the authentication flow. If the URL is of the format `https://yourapp.com/passwordless/verify`, the magic link sent to your user via email will be `https://yourapp.com/passwordless/verify?link_token=<link_token>`. Required if you selected Link or Link + OTP as your authentication method.<Badge variant="note" text="string" /> |
      | `template_variables` | No | Pass variables to be used in the email template sent to the user. You may include up to 30 key-value pairs to reference in the email template. <Badge variant="note" text="object" /> |

    </details>

    <details>
      <summary>Response parameters</summary>

      | Parameters | Description |
      |-----------|-------------|
      | `authRequestId` | Unique identifier for the magic link and OTP authentication request <Badge variant="note" text="string" /> |
      | `expiresAt` | Expiration time in seconds since epoch <Badge variant="note" text="number" /> |
      | `expiresIn` | Expiration time in seconds <Badge variant="note" text="number" /> |
      | `passwordlessType` | Type of magic link and OTP authentication (`OTP`, `LINK` or `LINK_OTP`) <Badge variant="note" text="string" /> |

    </details>
   ```python
   response = client.passwordless.send_passwordless_email(
       email="john.doe@example.com",
       template="SIGNIN",  # or "SIGNUP", "UNSPECIFIED"
       expires_in=300,
       magiclink_auth_uri="https://yourapp.com/passwordless/verify",
       template_variables={
           "employeeID": "EMP523",
           "teamName": "Alpha Team",
       },
   )

   # Extract auth request ID from response
   auth_request_id = response[0].auth_request_id
   ```
   ```go
    // Send a passwordless email (assumes you have an initialized `client` and `ctx`)
    templateType := scalekit.TemplateTypeSignin
    resp, err := scalekitClient.Passwordless().SendPasswordlessEmail(
      ctx,
      "john.doe@example.com",
      &scalekit.SendPasswordlessOptions{
        Template:          &templateType,
        State:             "jAy-state1-gM4fdZ...2nqm6Q",
        ExpiresIn:         300,
        MagiclinkAuthUri:  "https://yourapp.com/passwordless/verify", // required if Link or Link+OTP
        TemplateVariables: map[string]string{
          "employeeID": "EMP523",
          "teamName": "Alpha Team",
        },
      },
    )

    // resp contains: AuthRequestId, ExpiresAt, ExpiresIn, PasswordlessType
    ```
    <details>
      <summary>Request parameters</summary>
      | Parameter | Required | Description |
      |-----------|----------|-------------|
      | `email` | Yes | The email address to send the magic link or OTP verification code to <Badge variant="note" text="string" /> |
      | `MagiclinkAuthUri` | No | Magic Link URI for authentication <Badge variant="note" text="string" /> |
      | `State` | No | Optional state parameter <Badge variant="note" text="string" /> |
      | `Template` | No | Email template type (`SIGNIN`/`SIGNUP`) <Badge variant="note" text="string" /> |
      | `ExpiresIn` | No | Expiration time in seconds <Badge variant="note" text="number" /> |
      | `TemplateVariables` | No | Key-value pairs for email template <Badge variant="note" text="object" /> |
    </details>
    <details>
      <summary>Response parameters</summary>
      | Parameters | Description |
      |-----------|-------------|
      | `AuthRequestId` | Unique identifier for the magic link and OTP authentication request <Badge variant="note" text="string" /> |
      | `ExpiresAt` | Expiration time in seconds since epoch <Badge variant="note" text="number" /> |
      | `ExpiresIn` | Expiration time in seconds <Badge variant="note" text="number" /> |
      | `PasswordlessType` | Type of magic link and OTP authentication (`OTP`, `LINK` or `LINK_OTP`) <Badge variant="note" text="string" /> |
    </details>
   ```java
   import java.util.HashMap;
   import java.util.Map;

   TemplateType templateType = TemplateType.SIGNIN;
   Map<String, String> templateVariables = new HashMap<>();
   templateVariables.put("employeeID", "EMP523");
   templateVariables.put("teamName", "Alpha Team");

   SendPasswordlessOptions options = new SendPasswordlessOptions();
   options.setTemplate(templateType);
   options.setExpiresIn(300);
   options.setMagiclinkAuthUri("https://yourapp.com/passwordless/verify");
   options.setTemplateVariables(templateVariables);

   SendPasswordlessResponse response = passwordlessClient.sendPasswordlessEmail(
       "john.doe@example.com",
       options
   );

   String authRequestId = response.getAuthRequestId();
   ```
3. ### Resend a verification email

    Users can request a new verification email if they need one. Use the following endpoint to resend an OTP or magic link email.

    ```sh ins={5} "/api/v1/passwordless/email/resend" title="Request" wrap
    curl -L '<SCALEKIT_ENVIRONMENT_URL>/api/v1/passwordless/email/resend' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
    -d '{
        "auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"
    }'

   # Response

   # {

   # "auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"

   # "expires_at": "1748696575"

   # "expires_in": 300

   # "passwordless_type": "OTP" | "LINK" | "LINK_OTP"

   # }

    ```

    <details>
      <summary>Request parameters</summary>

      | Parameters | Required | Description |
      |-----------|----------|-------------|
      | `auth_request_id` | Yes | The unique identifier for the authentication request that was sent earlier <Badge variant="note" text="string" /> |
    </details>

    <details>
      <summary>Response parameters</summary>

      | Parameters | Description |
      |-----------|-------------|
      | `auth_request_id` | A unique identifier for the authentication request that can be used to verify the code <Badge variant="note" text="string" /> |
      | `expires_at` | Unix timestamp indicating when the verification code will expire <Badge variant="note" text="string" /> |
      | `expires_in` | The time in seconds after which the verification code will expire. Default is 300 seconds <Badge variant="note" text="number" /> |
      | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP` <Badge variant="note" text="string" /> |
    </details>
   ```js
    const { authRequestId } = sendResponse;
    const resendResponse = await scalekit.passwordless
    .resendPasswordlessEmail(
        authRequestId
    );

    // resendResponse = {
    //   authRequestId: "jAy-state1-gM4fdZ...2nqm6Q",
    //   expiresAt: "1748696575",
    //   expiresIn: "300",
    //   passwordlessType: "OTP" | "LINK" | "LINK_OTP"
    // }
    ```

    <details>
      <summary>Request parameters</summary>

      | Parameters | Required | Description |
      |-----------|----------|-------------|
      | `authRequestId` | Yes | The unique identifier for the authentication request that was sent earlier <Badge variant="note" text="string" /> |

    </details>

    <details>
      <summary>Response parameters</summary>

      | Parameters | Description |
      |-----------|-------------|
      | `authRequestId` | Unique identifier for the magic link and OTP authentication request <Badge variant="note" text="string" /> |
      | `expiresAt` | Expiration time in seconds since epoch <Badge variant="note" text="number" /> |
      | `expiresIn` | Expiration time in seconds. Default is 300 seconds <Badge variant="note" text="number" /> |
      | `passwordlessType` | `OTP`, `LINK` or `LINK_OTP` <Badge variant="note" text="string" /> |

    </details>
   ```python
   resend_response = client.passwordless.resend_passwordless_email(
       auth_request_id=auth_request_id,
   )

   new_auth_request_id = resend_response[0].auth_request_id
   ```
   ```go
    // Resend passwordless email for an existing auth request
    resendResp, err := scalekitClient.Passwordless().ResendPasswordlessEmail(
      ctx,            // context.Context (e.g., context.Background())
      authRequestId,  // string: from the send email response
    )

    if err != nil {
      // handle error (log, return HTTP 400/500, etc.)
      // ...
    }

    // resendResp is a pointer to ResendPasswordlessResponse struct:
    // type ResendPasswordlessResponse struct {
    //   AuthRequestId    string // Unique ID for the passwordless request
    //   ExpiresAt        int64  // Unix timestamp (seconds since epoch)
    //   ExpiresIn        int    // Expiry duration in seconds
    //   PasswordlessType string // "OTP", "LINK", or "LINK_OTP"
    // }
    ```

    <details>
      <summary>Request parameters</summary>

      | Parameters | Required | Description |
      |-----------|----------|-------------|
      | `authRequestId` | Yes | The unique identifier for the authentication request that was sent earlier <Badge variant="note" text="string" /> |

    </details>

    <details>
      <summary>Response parameters</summary>

      | Parameters | Description |
      |-----------|-------------|
      | `AuthRequestId` | Unique identifier for the magic link and OTP authentication request <Badge variant="note" text="string" /> |
      | `ExpiresAt` | Expiration time in seconds since epoch <Badge variant="note" text="number" /> |
      | `ExpiresIn` | Expiration time in seconds. Default is 300 seconds <Badge variant="note" text="number" /> |
      | `PasswordlessType` | `OTP`, `LINK` or `LINK_OTP` <Badge variant="note" text="string" /> |

    </details>
   ```java wrap showLineNumbers=false
   SendPasswordlessResponse resendResponse = passwordlessClient.resendPasswordlessEmail(authRequestId);
   ```
   If you enabled **Enable new Magic link & OTP credentials on resend** in the Scalekit dashboard, a new verification code or magic link will be sent each time the user requests a new one.
**Rate limits:** Scalekit enforces a rate limit of 2 magic link and OTP emails per minute per email address. This limit includes both initial sends and resends.

4. ### Verify the user's identity

    Once the user receives the verification email,
    - If it is a verification code, they'll enter it in your application. Use the following endpoint to validate the code and complete authentication.
    - If it is a magic link, they'll click the link in the email to verify their address. Capture the `link_token` query parameter and use it to verify.
    - For additional security with magic links, if you enabled "Enforce same browser origin" in the dashboard, include the `auth_request_id` in the verification request.

1. Create a form to collect the verification code
2. Call the verification API when the form is submitted to verify the code
3. Handle the response to either grant access or show an error
   ```http showLineNumbers=false title="API endpoint"
       POST /api/v1/passwordless/email/verify
       ```

       **Example implementation**

       ```sh ins={5,6} "/api/v1/passwordless/email/verify" title="Request" showLineNumbers=false wrap
           curl -L '<SCALEKIT_ENVIRONMENT_URL>/api/v1/passwordless/email/verify' \
           -H 'Content-Type: application/json' \
           -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
           -d '{
               "code": "123456",
               "auth_request_id": "YC4QR-dVZVtNNVHcHwrnHNDV..."
           }'
           ```
           <details>
             <summary>Request parameters</summary>
             | Parameters | Required | Description |
             |-----------|----------|-------------|
             | `code` | Yes | The verification code entered by the user <Badge variant="note" text="string" /> |
             | `auth_request_id` | Yes | The request ID from the response when the verification email was sent <Badge variant="note" text="string" /> |
           </details>

           <details>
             <summary>Response parameters</summary>
             | Parameters | Description |
             |-----------|-------------|
             | `email` | The email address of the user <Badge variant="note" text="string" /> |
             | `state` | The state parameter that was passed in the original request <Badge variant="note" text="string" /> |
             | `template` | The template that was used for the verification code <Badge variant="note" text="string" /> |
            | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP` <Badge variant="note" text="string" /> |
           </details>
         ```js wrap
           const { authRequestId } = sendResponse;
           const verifyResponse = await scalekit.passwordless
             .verifyPasswordlessEmail(
               { code: "123456"},
               authRequestId
             );

           // verifyResponse = {
           //   "email": "saifshine7@gmail.com",
           //   "state": "jAy-state1-gM4fdZdV22nqm6Q_j..",
           //   "template": "SIGNIN",
           //   "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
           // }
           ```
           <details>
             <summary>Request parameters</summary>
             | Parameters | Required | Description |
             |-----------|----------|-------------|
             | `options.code` | Yes | The verification code received by the user <Badge variant="note" text="string" /> |
             | `authRequestId` | Yes | The unique identifier for the authentication request that was sent earlier <Badge variant="note" text="string" /> |
           </details>

           <details>
             <summary>Response parameters</summary>
             | Parameters | Description |
             |-----------|-------------|
             | `email` | The email address of the user <Badge variant="note" text="string" /> |
             | `state` | The state parameter that was passed in the original request <Badge variant="note" text="string" /> |
             | `template` | The template that was used for the verification code <Badge variant="note" text="string" /> |
            | `passwordlessType` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP` <Badge variant="note" text="string" /> |
           </details>
         ```python
           verify_response = client.passwordless.verify_passwordless_email(
               code="123456",  # OTP code received via email
               auth_request_id=auth_request_id,
           )

           # User verified successfully
           user_email = verify_response[0].email
           ```
         ```go
             // Verify with OTP code
             verifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail(
                 ctx,
                 &scalekit.VerifyPasswordlessOptions{
                     Code:          "123456", // OTP code
                     AuthRequestId: authRequestId,
                 },
             )

             if err != nil {
                 // Handle error
                 return
             }

             // verifyResp contains the verified user's info
             // type VerifyPasswordLessResponse struct {
             //   Email            string
             //   State            string
             //   Template         string // SIGNIN | SIGNUP
             //   PasswordlessType string // OTP | LINK | LINK_OTP
             // }
             ```

             <details>
               <summary>Request parameters</summary>
               | Parameters | Required | Description |
               |-----------|----------|-------------|
               | `options.Code` | Yes | The verification code received by the user <Badge variant="note" text="string" /> |
               | `options.AuthRequestId` | Yes | The unique identifier for the authentication request that was sent earlier <Badge variant="note" text="string" /> |
             </details>

             <details>
               <summary>Response parameters</summary>
               | Parameters | Description |
               |-----------|-------------|
               | `Email` | The email address of the user <Badge variant="note" text="string" /> |
               | `State` | The state parameter that was passed in the original request <Badge variant="note" text="string" /> |
               | `Template` | The template that was used (`SIGNIN` or `SIGNUP`) <Badge variant="note" text="string" /> |
               | `PasswordlessType` | `OTP`, `LINK` or `LINK_OTP` <Badge variant="note" text="string" /> |
             </details>
           ```java
             // Verify with OTP code
             VerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();
             verifyOptions.setCode("123456"); // OTP code
             verifyOptions.setAuthRequestId(authRequestId);

             VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);

             // User verified successfully
             String userEmail = verifyResponse.getEmail();
             ```
           To support magic link verification, add a callback endpoint in your application typically at <code>https://your-app.com/passwordless/verify</code>. Implement it to verify the magic link token and complete the user authentication process.

1. Create a verification endpoint in your application to handle the magic link verification. This is the endpoint that the user lands in when they click the link in the email.
2. Capture the magic link token from the `link_token` request parameter from the URL.
3. Call the verification API when the user clicks the link in the email.
4. Based on token verification, complete the authentication process or show an error with an appropriate error message.
   ```http showLineNumbers=false title="API endpoint"
       POST /api/v1/passwordless/email/verify
       ```

       **Example implementation**

       ```sh ins={5,6} "/api/v1/passwordless/email/verify" title="Request" showLineNumbers=false
           curl -L '<SCALEKIT_ENVIRONMENT_URL>/api/v1/passwordless/email/verify' \
           -H 'Content-Type: application/json' \
           -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsIm..' \
           -d '{
               "link_token": "a4143d8f-...c846ed91e_l",
               "auth_request_id": "YC4QR-dVZVtNNVHcHwrnHNDV..." // (optional)
           }'
           ```
           <details>
             <summary>Request parameters</summary>
             | Parameters | Required | Description |
             |-----------|----------|-------------|
             | `link_token` | Yes | The link token received by the user <Badge variant="note" text="string" /> |
             | `auth_request_id` | No | The request ID you received when the verification email was sent. <Badge variant="note" text="string" /> |
**Auth request ID:** If you use Magic Link or Magic Link & OTP and have enabled same browser origin enforcement in the Scalekit dashboard, it is required to include the auth request ID in your request.
           </details>

           <details>
             <summary>Response parameters</summary>
             | Parameters | Description |
             |-----------|-------------|
             | `email` | The email address of the user <Badge variant="note" text="string" /> |
             | `state` | The state parameter that was passed in the original request <Badge variant="note" text="string" /> |
             | `template` | The template that was used for the verification code <Badge variant="note" text="string" /> |
            | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP` <Badge variant="note" text="string" /> |
           </details>
         ```js wrap showLineNumbers collapse={15-21}
           // User clicks the magic link in their email
           // Example magic link: https://yourapp.com/passwordless/verify?link_token=a4143d8f-d13d-415c-8f5a-5a5c846ed91e_l

           // 2. Express endpoint to handle the magic link verification
           app.get('/passwordless/verify', async (req, res) => {
             const { link_token } = req.query;

             try {
               // 3. Verify the magic link token with Scalekit
               const verifyResponse = await scalekit.passwordless
                 .verifyPasswordlessEmail(
                   { linkToken: link_token },
                   authRequestId // (optional) sendResponse.authRequestId
                 );

               // 4. Successfully log the user in
               // Set session/token and redirect to dashboard
               res.redirect('/dashboard');
             } catch (error) {
               res.status(400).json({
                    error: 'The magic link is invalid or has expired. Please request a new verification link.'
                });
             }
           });

           // verifyResponse = {
           //   "email": "saifshine7@gmail.com",
           //   "state": "jAy-state1-gM4fdZdV22nqm6Q_j..",
           //   "template": "SIGNIN",
           //   "passwordless_type": "OTP" | "LINK" | "LINK_OTP"
           // }
           ```
           <details>
             <summary>Request parameters</summary>
             | Parameters | Required | Description |
             |-----------|----------|-------------|
             | `options.linkToken` | Yes | The link token received by the user <Badge variant="note" text="string" /> |
             | `authRequestId` | No | The unique identifier for the authentication request that was sent earlier. <Badge variant="note" text="string" /> |
**Auth request ID:** If you use Magic Link or Magic Link & OTP and have enabled same browser origin enforcement in the Scalekit dashboard, it is required to include the auth request ID in your request.
           </details>

           <details>
             <summary>Response parameters</summary>
             | Parameters | Description |
             |-----------|-------------|
             | `email` | The email address of the user <Badge variant="note" text="string" /> |
             | `state` | The state parameter that was passed in the original request <Badge variant="note" text="string" /> |
             | `template` | The template that was used for the verification code <Badge variant="note" text="string" /> |
            | `passwordlessType` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP` <Badge variant="note" text="string" /> |
           </details>
         ```python
         # Verify with magic link token
         verify_response = client.passwordless.verify_passwordless_email(
             link_token=link_token,  # Magic link token from URL
             # auth_request_id=auth_request_id,  # optional if same-origin enforcement enabled
         )

         # User verified successfully
         user_email = verify_response[0].email
         ```
         ```go
         verifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail(
             ctx,
             &scalekit.VerifyPasswordlessOptions{
                 LinkToken: linkToken, // Magic link token
             },
         )

         if err != nil {
             // Handle error
             return
         }

         // User verified successfully
         userEmail := verifyResponse.Email
         ```
         ```java
         // Verify with magic link token
         VerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();
         verifyOptions.setLinkToken(linkToken); // Magic link token
         // verifyOptions.setAuthRequestId(authRequestId); // optional if same-origin enforcement enabled

         VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);

         // User verified successfully
         String userEmail = verifyResponse.getEmail();
         ```
**Validation attempt limits:** To protect your application, Scalekit allows a user only **five** attempts to enter the correct OTP within a ten-minute window.
     If the user exceeds this limit for an <code>auth_request_id</code>, the <code>/passwordless/email/verify</code> endpoint returns an **HTTP&nbsp;429&nbsp;Too&nbsp;Many&nbsp;Requests** error.
     To continue, the user must restart the authentication flow.

You've successfully implemented Magic link & OTP authentication in your application. Users can now sign in securely without passwords by entering a verification code (OTP) or clicking a magic link sent to their email.

---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
