> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended** (the `-y` flags skip prompts, so the command runs without an interactive terminal):
> ```bash
> npx -y @scalekit-inc/cli setup -y
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup -y
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents in `~/.agents/skills`.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Headless email API for magic link and OTP

Implement email OTP or magic link using direct API calls with full control over UX
Implement magic link and OTP authentication using Scalekit's headless APIs. Send either a one-time passcode (OTP) or a magic link to the user's email, 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.

** See the integration in action**

** Review the authentication sequence**

Coming soon

-------

1. ## Set up Scalekit

   Install the Scalekit SDK to your project.

   **Node.js**

     ```bash
     npm install @scalekit-sdk/node
     ```

      **Python**

      ```sh
      pip install scalekit-sdk-python
      ```

     **Go**

     ```sh
     go get -u github.com/scalekit-inc/scalekit-sdk-go/v2
     ```

      **Java**

      The Java SDK is published to Scalekit's Maven repository, `https://us-maven.pkg.dev/scalekit-public/java`. Add it to your build next to Maven Central. It needs no credentials. If your builds download dependencies only through a mirror such as Nexus or Artifactory, add this URL to the mirror instead. For Maven, add the `<repositories>` and `<dependency>` entries to the matching sections of your existing `pom.xml`.

      ```groovy title="build.gradle"
      // Gradle: build.gradle
      repositories {
          mavenCentral()
          maven { url "https://us-maven.pkg.dev/scalekit-public/java" }
      }

      dependencies {
          implementation "com.scalekit:scalekit-sdk-java:2.6.1"
      }
      ```

      ```xml title="pom.xml"
      <!-- Maven: pom.xml -->
      <repositories>
          <repository>
              <id>scalekit</id>
              <url>https://us-maven.pkg.dev/scalekit-public/java</url>
          </repository>
      </repositories>

      <dependencies>
          <dependency>
              <groupId>com.scalekit</groupId>
              <artifactId>scalekit-sdk-java</artifactId>
              <version>2.6.1</version>
          </dependency>
      </dependencies>
      ```

   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.

    > Image: Scalekit dashboard Magic link & OTP settings showing the login method selection and expiry period fields

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 title="API endpoint"
    POST /api/v1/passwordless/email/send
    ```

    **Example implementation**

   **cURL**

   ```sh title="Send a verification code to user's email"
     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"
   # }

   ```
   **Request parameters**

   | Parameter  | Required | Description |
         |------------|----------|-------------|
         | `email` | Yes | Recipient's email address  |
         | `expires_in` | No | Code expiration time in seconds (default: 300)  |
         | `state` | No | OIDC state parameter for request validation  |
         | `template` | No | Email template to use (`SIGNIN` or `SIGNUP`)  |
         | `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. |
         | `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.  |

   **Response parameters**

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

   **Node.js**

   ```js
   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"
   // }

   ```

    **Request parameters**

    | Parameter | Required | Description |
          |-----------|----------|-------------|
          | `email` | Yes | The email address to send the magic link or OTP verification code to  |
          | `template` | No | The template type (`SIGNIN`/`SIGNUP`)  |
          | `state` | No | Optional state parameter to maintain state between request and callback  |
          | `expiresIn` | No | Optional expiration time in seconds (default: 300)  |
          | `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. |
          | `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.  |

   **Response parameters**

   | Parameters | Description |
         |-----------|-------------|
         | `authRequestId` | Unique identifier for the magic link and OTP authentication request  |
         | `expiresAt` | Expiration time in seconds since epoch  |
         | `expiresIn` | Expiration time in seconds  |
         | `passwordlessType` | Type of magic link and OTP authentication (`OTP`, `LINK` or `LINK_OTP`)  |

   **Python**

   ```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**

   ```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
   ```
   **Request parameters**

   | Parameter | Required | Description |
         |-----------|----------|-------------|
         | `email` | Yes | The email address to send the magic link or OTP verification code to  |
         | `MagiclinkAuthUri` | No | Magic Link URI for authentication  |
         | `State` | No | Optional state parameter  |
         | `Template` | No | Email template type (`SIGNIN`/`SIGNUP`)  |
         | `ExpiresIn` | No | Expiration time in seconds  |
         | `TemplateVariables` | No | Key-value pairs for email template  |

   **Response parameters**

   | Parameters | Description |
         |-----------|-------------|
         | `AuthRequestId` | Unique identifier for the magic link and OTP authentication request  |
         | `ExpiresAt` | Expiration time in seconds since epoch  |
         | `ExpiresIn` | Expiration time in seconds  |
         | `PasswordlessType` | Type of magic link and OTP authentication (`OTP`, `LINK` or `LINK_OTP`)  |

   **Java**

   ```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.

   **cURL**

   ```sh title="Request"
   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"

   # }

   ```

   **Request parameters**

   | Parameters | Required | Description |
         |-----------|----------|-------------|
         | `auth_request_id` | Yes | The unique identifier for the authentication request that was sent earlier  |

   **Response parameters**

   | Parameters | Description |
         |-----------|-------------|
         | `auth_request_id` | A unique identifier for the authentication request that can be used to verify the code  |
         | `expires_at` | Unix timestamp indicating when the verification code will expire  |
         | `expires_in` | The time in seconds after which the verification code will expire. Default is 300 seconds  |
         | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP`  |

   **Node.js**

   ```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"
   // }
   ```

   **Request parameters**

   | Parameters | Required | Description |
         |-----------|----------|-------------|
         | `authRequestId` | Yes | The unique identifier for the authentication request that was sent earlier  |

   **Response parameters**

   | Parameters | Description |
         |-----------|-------------|
         | `authRequestId` | Unique identifier for the magic link and OTP authentication request  |
         | `expiresAt` | Expiration time in seconds since epoch  |
         | `expiresIn` | Expiration time in seconds. Default is 300 seconds  |
         | `passwordlessType` | `OTP`, `LINK` or `LINK_OTP`  |

   **Python**

   ```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**

   ```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"
   // }
   ```

   **Request parameters**

   | Parameters | Required | Description |
         |-----------|----------|-------------|
         | `authRequestId` | Yes | The unique identifier for the authentication request that was sent earlier  |

   **Response parameters**

   | Parameters | Description |
         |-----------|-------------|
         | `AuthRequestId` | Unique identifier for the magic link and OTP authentication request  |
         | `ExpiresAt` | Expiration time in seconds since epoch  |
         | `ExpiresIn` | Expiration time in seconds. Default is 300 seconds  |
         | `PasswordlessType` | `OTP`, `LINK` or `LINK_OTP`  |

   **Java**

   ```java
   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.

    > caution: 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.

     **Verification code**

       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 title="API endpoint"
     POST /api/v1/passwordless/email/verify
     ```

     **Example implementation**

       **cURL**

```sh title="Request"
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..."
}'
```
  **Request parameters**

  | Parameters | Required | Description |
               |-----------|----------|-------------|
               | `code` | Yes | The verification code entered by the user  |
               | `auth_request_id` | Yes | The request ID from the response when the verification email was sent  |

  **Response parameters**

  | Parameters | Description |
               |-----------|-------------|
               | `email` | The email address of the user  |
               | `state` | The state parameter that was passed in the original request  |
               | `template` | The template that was used for the verification code  |
              | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP`  |

  **Node.js**

  ```js
  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"
  // }
  ```
  **Request parameters**

  | Parameters | Required | Description |
               |-----------|----------|-------------|
               | `options.code` | Yes | The verification code received by the user  |
               | `authRequestId` | Yes | The unique identifier for the authentication request that was sent earlier  |

  **Response parameters**

  | Parameters | Description |
               |-----------|-------------|
               | `email` | The email address of the user  |
               | `state` | The state parameter that was passed in the original request  |
               | `template` | The template that was used for the verification code  |
              | `passwordlessType` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP`  |

  **Python**

  ```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**

    ```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
    // }
    ```

    **Request parameters**

    | Parameters | Required | Description |
                   |-----------|----------|-------------|
                   | `options.Code` | Yes | The verification code received by the user  |
                   | `options.AuthRequestId` | Yes | The unique identifier for the authentication request that was sent earlier  |

    **Response parameters**

    | Parameters | Description |
                   |-----------|-------------|
                   | `Email` | The email address of the user  |
                   | `State` | The state parameter that was passed in the original request  |
                   | `Template` | The template that was used (`SIGNIN` or `SIGNUP`)  |
                   | `PasswordlessType` | `OTP`, `LINK` or `LINK_OTP`  |

    **Java**

    ```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();
    ```

    **Magic link verification**

    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 title="API endpoint"
    POST /api/v1/passwordless/email/verify
    ```

    **Example implementation**

      **cURL**

```sh title="Request"
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)
}'
```
 **Request parameters**

 | Parameters | Required | Description |
              |-----------|----------|-------------|
              | `link_token` | Yes | The link token received by the user  |
              | `auth_request_id` | No | The request ID you received when the verification email was sent.  |

              > caution: 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.

 **Response parameters**

 | Parameters | Description |
              |-----------|-------------|
              | `email` | The email address of the user  |
              | `state` | The state parameter that was passed in the original request  |
              | `template` | The template that was used for the verification code  |
             | `passwordless_type` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP`  |

  **Node.js**

  ```js
  // 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"
  // }
  ```
  **Request parameters**

  | Parameters | Required | Description |
               |-----------|----------|-------------|
               | `options.linkToken` | Yes | The link token received by the user  |
               | `authRequestId` | No | The unique identifier for the authentication request that was sent earlier.  |
              > caution: 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.

  **Response parameters**

  | Parameters | Description |
               |-----------|-------------|
               | `email` | The email address of the user  |
               | `state` | The state parameter that was passed in the original request  |
               | `template` | The template that was used for the verification code  |
              | `passwordlessType` | The type of magic link and OTP authentication. Currently supports `OTP`, `LINK` and `LINK_OTP`  |

  **Python**

  ```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**

  ```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**

  ```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();
  ```

   > caution: 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 |
