Build with a coding agent
npm install -g @scalekit-inc/cliscalekit setupnpx @scalekit-inc/cli setupImplement 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.
Coming soon
npm install -g @scalekit-inc/cliscalekit setupnpx @scalekit-inc/cli setupInstall the Scalekit SDK to your project.
npm install @scalekit-sdk/nodepip install scalekit-sdk-pythongo get -u github.com/scalekit-inc/scalekit-sdk-go/* Gradle users - add the following to your dependencies in build file */implementation "com.scalekit:scalekit-sdk-java:2.1.3"<!-- Maven users - add the following to your `pom.xml` --><dependency> <groupId>com.scalekit</groupId> <artifactId>scalekit-sdk-java</artifactId> <version>2.1.3</version></dependency>Your application is responsible for verifying users and initiating sessions, while Scalekit securely manages authentication tokens to ensure the verification process is completed successfully
In the Scalekit dashboard, enable magic link and OTP and choose your login method.
Optional security settings:

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:
POST /api/v1/passwordless/email/sendExample implementation
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" } }'
# Response6 collapsed lines
# {# "auth_request_id": "jAy-state1-gM4fdZ...2nqm6Q"# "expires_at": "1748696575"# "expires_in": 100# "passwordless_type": "OTP" | "LINK" | "LINK_OTP"# }| Parameter | Required | Description |
|---|---|---|
email | Yes | Recipient’s email address string |
expires_in | No | Code expiration time in seconds (default: 300) number |
state | No | OIDC state parameter for request validation string |
template | No | Email template to use (SIGNIN or SIGNUP) 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.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. object |
| Parameters | Description |
|---|---|
auth_request_id | A unique identifier for the authentication request that can be used to verify the code string |
expires_at | Unix timestamp indicating when the verification code will expire string |
expires_in | The time in seconds after which the verification code will expire. Default is 100 seconds number |
passwordless_type | The type of magic link and OTP authentication. Currently supports OTP, LINK and LINK_OTP string |
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"// }| Parameter | Required | Description |
|---|---|---|
email | Yes | The email address to send the magic link or OTP verification code to string |
template | No | The template type (SIGNIN/SIGNUP) string |
state | No | Optional state parameter to maintain state between request and callback string |
expiresIn | No | Optional expiration time in seconds (default: 300) 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.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. object |
| Parameters | Description |
|---|---|
authRequestId | Unique identifier for the magic link and OTP authentication request string |
expiresAt | Expiration time in seconds since epoch number |
expiresIn | Expiration time in seconds number |
passwordlessType | Type of magic link and OTP authentication (OTP, LINK or LINK_OTP) string |
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 responseauth_request_id = response[0].auth_request_id// Send a passwordless email (assumes you have an initialized `client` and `ctx`)templateType := scalekit.TemplateTypeSigninresp, 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| Parameter | Required | Description |
|---|---|---|
email | Yes | The email address to send the magic link or OTP verification code to string |
MagiclinkAuthUri | No | Magic Link URI for authentication string |
State | No | Optional state parameter string |
Template | No | Email template type (SIGNIN/SIGNUP) string |
ExpiresIn | No | Expiration time in seconds number |
TemplateVariables | No | Key-value pairs for email template object |
| Parameters | Description |
|---|---|
AuthRequestId | Unique identifier for the magic link and OTP authentication request string |
ExpiresAt | Expiration time in seconds since epoch number |
ExpiresIn | Expiration time in seconds number |
PasswordlessType | Type of magic link and OTP authentication (OTP, LINK or LINK_OTP) string |
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();Users can request a new verification email if they need one. Use the following endpoint to resend an OTP or magic link email.
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"
# }| Parameters | Required | Description |
|---|---|---|
auth_request_id | Yes | The unique identifier for the authentication request that was sent earlier string |
| Parameters | Description |
|---|---|
auth_request_id | A unique identifier for the authentication request that can be used to verify the code string |
expires_at | Unix timestamp indicating when the verification code will expire string |
expires_in | The time in seconds after which the verification code will expire. Default is 300 seconds number |
passwordless_type | The type of magic link and OTP authentication. Currently supports OTP, LINK and LINK_OTP string |
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"// }| Parameters | Required | Description |
|---|---|---|
authRequestId | Yes | The unique identifier for the authentication request that was sent earlier string |
| Parameters | Description |
|---|---|
authRequestId | Unique identifier for the magic link and OTP authentication request string |
expiresAt | Expiration time in seconds since epoch number |
expiresIn | Expiration time in seconds. Default is 300 seconds number |
passwordlessType | OTP, LINK or LINK_OTP string |
resend_response = client.passwordless.resend_passwordless_email( auth_request_id=auth_request_id,)
new_auth_request_id = resend_response[0].auth_request_id// Resend passwordless email for an existing auth requestresendResp, 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"// }| Parameters | Required | Description |
|---|---|---|
authRequestId | Yes | The unique identifier for the authentication request that was sent earlier string |
| Parameters | Description |
|---|---|
AuthRequestId | Unique identifier for the magic link and OTP authentication request string |
ExpiresAt | Expiration time in seconds since epoch number |
ExpiresIn | Expiration time in seconds. Default is 300 seconds number |
PasswordlessType | OTP, LINK or LINK_OTP string |
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.
Once the user receives the verification email,
link_token query parameter and use it to verify.auth_request_id in the verification request.POST /api/v1/passwordless/email/verifyExample implementation
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..."}'| Parameters | Required | Description |
|---|---|---|
code | Yes | The verification code entered by the user string |
auth_request_id | Yes | The request ID from the response when the verification email was sent string |
| Parameters | Description |
|---|---|
email | The email address of the user string |
state | The state parameter that was passed in the original request string |
template | The template that was used for the verification code string |
passwordless_type | The type of magic link and OTP authentication. Currently supports OTP, LINK and LINK_OTP string |
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"// }| Parameters | Required | Description |
|---|---|---|
options.code | Yes | The verification code received by the user string |
authRequestId | Yes | The unique identifier for the authentication request that was sent earlier string |
| Parameters | Description |
|---|---|
email | The email address of the user string |
state | The state parameter that was passed in the original request string |
template | The template that was used for the verification code string |
passwordlessType | The type of magic link and OTP authentication. Currently supports OTP, LINK and LINK_OTP string |
verify_response = client.passwordless.verify_passwordless_email( code="123456", # OTP code received via email auth_request_id=auth_request_id,)
# User verified successfullyuser_email = verify_response[0].email// Verify with OTP codeverifyResponse, 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// }| Parameters | Required | Description |
|---|---|---|
options.Code | Yes | The verification code received by the user string |
options.AuthRequestId | Yes | The unique identifier for the authentication request that was sent earlier string |
| Parameters | Description |
|---|---|
Email | The email address of the user string |
State | The state parameter that was passed in the original request string |
Template | The template that was used (SIGNIN or SIGNUP) string |
PasswordlessType | OTP, LINK or LINK_OTP string |
// Verify with OTP codeVerifyPasswordlessOptions verifyOptions = new VerifyPasswordlessOptions();verifyOptions.setCode("123456"); // OTP codeverifyOptions.setAuthRequestId(authRequestId);
VerifyPasswordLessResponse verifyResponse = passwordlessClient.verifyPasswordlessEmail(verifyOptions);
// User verified successfullyString userEmail = verifyResponse.getEmail();To support magic link verification, add a callback endpoint in your application typically at https://your-app.com/passwordless/verify. Implement it to verify the magic link token and complete the user authentication process.
link_token request parameter from the URL.POST /api/v1/passwordless/email/verifyExample implementation
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)}'| Parameters | Required | Description |
|---|---|---|
link_token | Yes | The link token received by the user string |
auth_request_id | No | The request ID you received when the verification email was sent. string |
| Parameters | Description |
|---|---|
email | The email address of the user string |
state | The state parameter that was passed in the original request string |
template | The template that was used for the verification code string |
passwordless_type | The type of magic link and OTP authentication. Currently supports OTP, LINK and LINK_OTP string |
// 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 verificationapp.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 );7 collapsed lines
// 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"// }| Parameters | Required | Description |
|---|---|---|
options.linkToken | Yes | The link token received by the user string |
authRequestId | No | The unique identifier for the authentication request that was sent earlier. string |
| Parameters | Description |
|---|---|
email | The email address of the user string |
state | The state parameter that was passed in the original request string |
template | The template that was used for the verification code string |
passwordlessType | The type of magic link and OTP authentication. Currently supports OTP, LINK and LINK_OTP string |
# Verify with magic link tokenverify_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 successfullyuser_email = verify_response[0].emailverifyResponse, err := scalekitClient.Passwordless().VerifyPasswordlessEmail( ctx, &scalekit.VerifyPasswordlessOptions{ LinkToken: linkToken, // Magic link token },)
if err != nil { // Handle error return}
// User verified successfullyuserEmail := verifyResponse.Email// Verify with magic link tokenVerifyPasswordlessOptions 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 successfullyString userEmail = verifyResponse.getEmail();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.