Skip to main content
Login uses a three-step flow: initialize a session token, submit credentials to receive a 2FA challenge, then verify the challenge to receive tokens.

Request

Step 1 — Initialize login

POST /api/v1/auth/login/init
No request body or authentication required. Returns a short-lived login_token (valid for 3 minutes) that must be included in Step 2. Response
token
string
required
Short-lived login token to include in the credential submission.
expires_at
string
required
ISO 8601 expiration timestamp for the login token.
{
  "token": "lt_a1b2c3d4e5f6",
  "expires_at": "2024-01-15T10:03:00Z"
}

Step 2 — Submit credentials

POST /api/v1/auth/login
No authentication required.
email
string
required
The user’s email address.
password
string
required
The user’s password (minimum 8 characters).
login_token
string
required
The one-time token obtained from POST /api/v1/auth/login/init.
Response
challenge_id
string
required
Identifier for the 2FA challenge. Pass this to Step 3.
method
string
required
The 2FA method issued: totp or email_otp.
expires_at
string
required
ISO 8601 expiration timestamp for the challenge.
backup_code_allowed
boolean
required
Whether the user may verify using a backup code.
user
object
required
{
  "challenge_id": "ch_abc123",
  "method": "email_otp",
  "expires_at": "2024-01-15T10:15:00Z",
  "backup_code_allowed": true,
  "user": {
    "id": "usr_xyz789",
    "email": "jane@example.com",
    "full_name": "Jane Doe",
    "hasOnboarded": false
  }
}

Step 3 — Verify 2FA challenge

POST /api/v1/auth/login/2fa/verify
No authentication required.
challenge_id
string
required
The challenge ID from Step 2.
code
string
required
The verification code (OTP, TOTP code, or backup code). Must be between 4 and 32 characters.
code_type
string
required
The type of code being submitted. primary for OTP/TOTP, backup for a backup code.
Response
access_token
string
required
Short-lived JWT access token (valid for 15 minutes). Pass as Authorization: Bearer <token> on authenticated requests.
refresh_token
string
required
Long-lived refresh token (valid for 30 days). Use to obtain a new access token without re-authenticating.
user
object
required
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "rt_a1b2c3d4e5f6g7h8i9j0",
  "user": {
    "id": "usr_xyz789",
    "email": "jane@example.com",
    "full_name": "Jane Doe",
    "hasOnboarded": false
  }
}

Refresh tokens

Exchange an expired access token for a new pair without re-authenticating. The old refresh token is immediately revoked.
POST /api/v1/auth/refresh
refresh_token
string
required
A valid, unexpired refresh token.
Response
access_token
string
required
New short-lived JWT access token.
refresh_token
string
required
New refresh token (the submitted token is revoked).
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "rt_newtoken123"
}

Logout

POST /api/v1/auth/logout
Requires a valid Authorization: Bearer <access_token> header.
refresh_token
string
The refresh token to revoke. Required when logout_all_devices is false.
logout_all_devices
boolean
default:"false"
When true, all refresh tokens for the authenticated user are revoked across every device.
Response
success
boolean
required
true when logout completed successfully.
message
string
required
Confirmation message.
{
  "success": true,
  "message": "Logged out successfully"
}

Resend 2FA code

If the email OTP expires or is not delivered, resend it for an active challenge.
POST /api/v1/auth/login/2fa/resend
challenge_id
string
required
The challenge ID from Step 2.

Error codes

StatusDescription
400Missing or invalid fields, or invalid/expired login_token.
401Invalid credentials.
403Account locked after 5 failed attempts (locked for 6 hours), or email not verified.
429Rate limit exceeded (3 attempts per minute per IP and email).

Example

1

Initialize a login session

curl --request POST \
  --url https://api.syncgrampay.com/api/v1/auth/login/init
2

Submit credentials

curl --request POST \
  --url https://api.syncgrampay.com/api/v1/auth/login \
  --header 'Content-Type: application/json' \
  --data '{
    "email": "jane@example.com",
    "password": "supersecret",
    "login_token": "lt_a1b2c3d4e5f6"
  }'
3

Verify the 2FA challenge

curl --request POST \
  --url https://api.syncgrampay.com/api/v1/auth/login/2fa/verify \
  --header 'Content-Type: application/json' \
  --data '{
    "challenge_id": "ch_abc123",
    "code": "847201",
    "code_type": "primary"
  }'