Authentication — Merchant API
One login call gets you a JWT that's valid on every other Merchant API route. There's no separate API key, no request signing, no encryption — just the token on every call after this one.
Login
POST /login
Content-Type: application/json
{
"emailOrMobile": "merchant@example.com",
"password": "your-password"
}
Response:
{
"statusCode": "00",
"message": "success",
"token": "eyJhbGciOiJIUzI1NiJ9...",
"user": { "name": "...", "role": "merchant", "merchantId": "...", "outletId": "..." }
}
Send emailOrMobile as an email address or a bare mobile number — the backend tells them apart by whether the value contains @.
Using the token
Authorization: Bearer <token>
Attach this header to every AEPS/DMT/UPI call — nothing else is required. The token itself carries your merchantId, so you never put it in a request body (that's a Partner API detail, for a caller acting on behalf of more than one merchant — see Partner API Authentication).
Session lifetime
- The JWT is signed HS256 and carries
role,merchantId,outletId,aeskey, and asessionToken. - Single-session enforcement: logging in again immediately invalidates the previous token (the new
sessionTokenoverwrites the old one in Redis). A displaced token doesn't quietly keep working — its next use gets401 { statusCode: "SESSION_DISPLACED" }. If you see that error, it means a newer login happened (a different device, a retried login, etc.), not a generic expiry — re-authenticate to get a fresh token rather than retrying the old one. - Re-authenticate on any
401in general; there's no refresh-token flow — logging in again is how you get a new token.
OTP flows
Used for merchant-level actions that need a one-time password (separate from AEPS/DMT's own onboarding OTP steps, which are documented on their respective pages):
POST /send-otp
{ "mobile": "9876543210" }
POST /verify-otp
{ "mobile": "9876543210", "otp": "123456" }
Source: paymentSystem/src/modules/auth/auth.routes.js.