MailLandDocs

Authentication

Authenticate with the Mail.Land API using Bearer tokens or API keys

MailLand supports two authentication methods: Bearer tokens (JWT) for user sessions and API keys for server-to-server integration. Dashboard sessions send Authorization: Bearer <token>; server-to-server calls send X-API-Key: <key>.

Authentication Methods

MethodHeaderUse Case
Bearer TokenAuthorization: Bearer <token>User sessions, dashboard
API KeyX-API-Key: <key>Server-to-server, automation

Register

POST https://api.mail.land/v1/auth/register
Registration request
{
  "email": "[email protected]",
  "password": "SecureP@ssw0rd!",
  "fullName": "John Doe",
  "companyName": "My Company"
}
Register
curl -X POST https://api.mail.land/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "SecureP@ssw0rd!",
    "fullName": "John Doe",
    "companyName": "My Company"
  }'
FieldTypeRequiredDescription
emailstringYesEmail address
passwordstringYesPassword (8–128 characters; needs an uppercase letter, a lowercase letter, a digit and a special character)
fullNamestringNoFull name
companyNamestringNoCompany / workspace name

Unknown fields (such as a tenantName sent by mistake) are dropped silently.

Registration response
{
  "requiresEmailVerification": true,
  "verificationTicket": "vt_abc123...",
  "maskedEmail": "u***@example.com"
}

The response status is 201.

After registration, verify the email address before logging in.

Email Verification

Registration emails a 6-digit code. Call POST /v1/auth/verify-email-ticket with { "ticket": "<verificationTicket>", "code": "123456" }; on success it returns the login token response.

POST https://api.mail.land/v1/auth/verify-email-ticket
Verify email request
{
  "ticket": "vt_abc123...",
  "code": "123456"
}
Verify email
curl -X POST https://api.mail.land/v1/auth/verify-email-ticket \
  -H "Content-Type: application/json" \
  -d '{"ticket": "vt_abc123...", "code": "123456"}'

Resend the verification email: POST /v1/auth/resend-verification-ticket with { "ticket": "..." }. It is rate-limited and returns a fresh ticket.

POST https://api.mail.land/v1/auth/resend-verification-ticket
Resend verification request
{
  "ticket": "vt_abc123..."
}
Resend verification
curl -X POST https://api.mail.land/v1/auth/resend-verification-ticket \
  -H "Content-Type: application/json" \
  -d '{"ticket": "vt_abc123..."}'

Login

POST https://api.mail.land/v1/auth/login
Login request
{
  "email": "[email protected]",
  "password": "SecureP@ssw0rd!"
}
Login
curl -X POST https://api.mail.land/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "SecureP@ssw0rd!"
  }'

Response

The login response varies based on the account state:

Successful login:

Login response
{
  "userId": "v4j2x7n1m9k5p3",
  "email": "[email protected]",
  "fullName": "John Doe",
  "avatarUrl": null,
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "rt_a1b2c3d4e5...",
  "expiresAt": "2026-09-25T13:00:00Z",
  "requiresTwoFactor": false,
  "twoFactorChallengeId": null,
  "tenantCount": 1,
  "activeTenantId": "k3m9x2p7qw4rtz",
  "activeTenantName": "My Company",
  "requiresEmailVerification": false,
  "verificationTicket": null,
  "maskedEmail": null
}

There is no user object and no role in the response. An unverified account gets 200 with requiresEmailVerification: true and a verificationTicket, not an error.

Two-factor authentication required:

2FA required response
{
  "accessToken": null,
  "refreshToken": null,
  "requiresTwoFactor": true,
  "twoFactorChallengeId": "2fa_challenge_abc123"
}

When requiresTwoFactor is true, complete the 2FA verification before receiving tokens.

Refresh Token

Access tokens expire after 60 minutes. Use the refresh token to obtain a new access token:

POST https://api.mail.land/v1/auth/refresh-token
Refresh token request
{
  "refreshToken": "rt_a1b2c3d4e5..."
}
Refresh token
curl -X POST https://api.mail.land/v1/auth/refresh-token \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "rt_a1b2c3d4e5..."}'

The response is the same shape as the login response above (the full AuthResponse), not just these three fields:

Refresh response
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "rt_new_token...",
  "expiresAt": "2026-09-25T14:00:00Z"
}

Each refresh token can only be used once. The response includes a new refresh token for the next rotation.

Logout

Invalidate the current session:

POST https://api.mail.land/v1/auth/logout

Send { "refreshToken": "..." } in the request body (or the refresh-token cookie). The refresh token is revoked; the access token stays valid until it expires: a Bearer header alone revokes nothing.

Logout
curl -X POST https://api.mail.land/v1/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Revoke all active sessions:

POST https://api.mail.land/v1/auth/revoke-all-tokens
Revoke all sessions
curl -X POST https://api.mail.land/v1/auth/revoke-all-tokens \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Password Management

Forgot Password

POST https://api.mail.land/v1/auth/forgot-password
Forgot password request
{
  "email": "[email protected]"
}
Forgot password
curl -X POST https://api.mail.land/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'

Sends a password reset email with a time-limited token.

Reset Password

POST https://api.mail.land/v1/auth/reset-password

confirmPassword is required and must match newPassword; the same complexity rules apply as at registration.

Reset password request
{
  "token": "reset-token-from-email",
  "newPassword": "NewSecureP@ssw0rd!",
  "confirmPassword": "NewSecureP@ssw0rd!"
}
Reset password
curl -X POST https://api.mail.land/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "reset-token-from-email",
    "newPassword": "NewSecureP@ssw0rd!",
    "confirmPassword": "NewSecureP@ssw0rd!"
  }'

Validate Reset Token

Check if a reset token is still valid before showing the reset form:

GET https://api.mail.land/v1/auth/validate-reset-token?token=reset-token-from-email
Validate reset token
curl "https://api.mail.land/v1/auth/validate-reset-token?token=reset-token-from-email"
Validate token response
{
  "valid": true,
  "message": "Token is valid"
}

Change Password

Change the password for the currently authenticated user:

POST https://api.mail.land/v1/auth/change-password
Change password request
{
  "currentPassword": "OldP@ssw0rd!",
  "newPassword": "NewSecureP@ssw0rd!"
}
Change password
curl -X POST https://api.mail.land/v1/auth/change-password \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "currentPassword": "OldP@ssw0rd!",
    "newPassword": "NewSecureP@ssw0rd!"
  }'

Profile Management

Get Current User

GET https://api.mail.land/v1/auth/me
Get current user
curl https://api.mail.land/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Current user response
{
  "id": "v4j2x7n1m9k5p3",
  "email": "[email protected]",
  "fullName": "John Doe",
  "companyName": "Acme",
  "avatarUrl": "https://cdn.mail.land/avatars/usr_a1b2c3d4e5.jpg",
  "status": "Active",
  "createdAt": "2026-01-15T08:00:00Z",
  "subscription": {
    "planName": "Pro",
    "monthlyEmailLimit": 50000,
    "rateLimitPerSecond": 10,
    "startDate": "2026-01-15T08:00:00Z",
    "endDate": "2026-10-15T08:00:00Z",
    "status": "Active"
  },
  "stats": {
    "credentialCount": 3,
    "domainCount": 2
  }
}

There is no firstName, lastName, phone, tenantId, role or twoFactorEnabled field in this response.

Update Profile

PUT https://api.mail.land/v1/auth/profile

fullName is required; omitting companyName clears it. The update matches the user's row by user id alone, so it works from any workspace the user is active in, not only the one the account was created in.

Update profile request
{
  "fullName": "Jane Smith",
  "companyName": "Acme"
}
Update profile
curl -X PUT https://api.mail.land/v1/auth/profile \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Jane Smith",
    "companyName": "Acme"
  }'

Upload Avatar

POST https://api.mail.land/v1/account/avatar

Send a multipart/form-data request with the avatar image file. Supported formats: JPEG, PNG, GIF or WebP (the content is sniffed, not just the extension). Maximum size: 1 MB.

Upload avatar
curl -X POST https://api.mail.land/v1/account/avatar \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -F "file=@/path/to/avatar.jpg"

Social Authentication

MailLand supports social login via Google, GitHub, and Microsoft:

Redirect to provider

GET https://api.mail.land/auth/social/{provider}

Where {provider} is google, github, or microsoft. This redirects the user to the provider's OAuth consent screen.

Redirect to social provider
curl https://api.mail.land/auth/social/google

Handle callback

After the user authorizes, the provider redirects back to MailLand with an authorization code:

GET https://api.mail.land/auth/social/{provider}/callback?code=...
Social callback
curl "https://api.mail.land/auth/social/google/callback?code=authorization-code-from-provider"

The callback is handled by MailLand, which redirects the browser to {frontend}/{locale}/auth/callback?code=<one-time code>&provider=.... That code is MailLand's own one-time code, not the provider's authorization code: pass it, unmodified, to the exchange step below.

Exchange for tokens

POST https://api.mail.land/v1/auth/social/exchange
Social exchange request
{
  "code": "one-time-code-from-callback-redirect"
}
Exchange social code for tokens
curl -X POST https://api.mail.land/v1/auth/social/exchange \
  -H "Content-Type: application/json" \
  -d '{"code": "one-time-code-from-callback-redirect"}'

Returns the same AuthResponse as regular login.

Delete Account

Deletion is per workspace, not per user account, and only a workspace Owner can start it. It schedules deletion after a grace period rather than deleting immediately:

POST https://api.mail.land/v1/account/lifecycle/schedule-deletion
Schedule deletion request
{
  "gracePeriodDays": 30
}
Schedule deletion
curl -X POST https://api.mail.land/v1/account/lifecycle/schedule-deletion \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"gracePeriodDays": 30}'

gracePeriodDays accepts 7–90 (default 30); values outside that range are clamped, not refused. Cancel the pending deletion any time before the grace period ends:

POST https://api.mail.land/v1/account/lifecycle/cancel-deletion

Workspace Owners schedule deletion with POST /v1/account/lifecycle/schedule-deletion (gracePeriodDays 7–90, default 30, out-of-range values clamped) and can cancel it with POST /v1/account/lifecycle/cancel-deletion until then. There is no DELETE /v1/auth/delete-account endpoint, and this action is not per-user: it deletes the whole workspace once the grace period elapses.

Error Responses

StatusCodeDescription
400VALIDATION_FAILEDRegistration request failed validation, with an errors[] array
401UNAUTHORIZEDWrong email or password
403FORBIDDENAccount is inactive
409CONFLICTEmail already registered
429TOO_MANY_LOGIN_ATTEMPTSToo many failed login attempts; response includes Retry-After

An unverified email does not return an error: login responds 200 with requiresEmailVerification: true and a fresh verificationTicket.

Next Steps

On this page