PA

PayAssure

Developer Dashboard

API reference / Auth

Authentication API

User registration, login, token rotation, logout, password recovery, and business integration authentication for PayAssure.

Authentication model

The platform has two related credential types:

  • User credentials: username or email plus password. Successful login returns a JWT access token and refresh token.
  • Business API credentials: API key and API secret for settlement integrations. Business authentication is documented below.

Protected route headers

Authorization: Bearer <accessToken>
Content-Type: application/json

Access tokens expire after 15 minutes. Refresh tokens expire after 7 days. The server checks refreshTokenVersion on every protected request, so logout or refresh rotation invalidates the previous token version.

Common error response

{
  "statusCode": 401,
  "message": "Invalid credentials",
  "error": "Unauthorized"
}

Validation error response

{
  "statusCode": 400,
  "message": ["email must be an email", "password must be longer than or equal to 6 characters"],
  "error": "Bad Request",
  "path": "/auth/register-before-onboarding"
}

Request validation removes unknown body properties. Registration passwords require at least 6 characters; login only requires a string. Identifiers contain either a username or email address.

POST/auth/register-before-onboarding

Register before onboarding

Creates an active standard USER account before the participant completes onboarding. No bearer token is required.

Request

{
  "username": "merchant-user",
  "email": "merchant@example.com",
  "password": "strong-password"
}

If the email already belongs to an onboarding participant and an account exists, the participant is linked and 201 is returned with message: Account already exists. Please complete onboarding to finish your profile.

Success response

{
  "message": "Account created successfully. Your profile is incomplete. Please complete onboarding to finish setup.",
  "profileComplete": false,
  "user": {
  "id": "clx123user",
  "username": "merchant-user",
  "email": "merchant@example.com",
  "role": "USER"
}
}

Errors

  • 400 Bad Request: body validation failed.
  • 409 Conflict: Email already exists or Username already exists.
POST/auth/onboarded-register

Register after onboarding

Creates a standard USER account for an existing onboarding participant, or links an existing account to that participant. An onboarding record must exist for the submitted email. No bearer token is required.

Request

{
  "username": "merchant-user",
  "email": "merchant@example.com",
  "password": "strong-password"
}

Success response

New account:
{
  "message": "User account created successfully and profile is complete.",
  "profileComplete": true,
  "user": {
  "id": "clx123user",
  "username": "merchant-user",
  "email": "merchant@example.com",
  "role": "USER"
}
}

Existing account linked:
{
  "message": "Your profile is now complete.",
  "profileComplete": true,
  "user": {
  "id": "clx123user",
  "username": "merchant-user",
  "email": "merchant@example.com",
  "role": "USER"
}
}

Errors

  • 400 Bad Request: body validation failed.
  • 401 Unauthorized: no onboarding record exists. Message: No onboarding record found for this email address.
  • 409 Conflict: conflicting email or username. Message: Email already exists or Username already exists.
POST/auth/login

Login

Authenticates an active user using either their username or email address. No bearer token is required.

Request

{
  "identifier": "merchant-user",
  "password": "strong-password"
}

The identifier may instead be an email address. For incomplete onboarding, profileComplete is false and the message is: Your profile is incomplete. Please finish onboarding to get API keys access.

Success response

{
  "accessToken": "<jwt-access-token>",
  "refreshToken": "<jwt-refresh-token>",
  "profileComplete": true,
  "message": "Profile complete.",
  "user": {
  "id": "clx123user",
  "username": "merchant-user",
  "email": "merchant@example.com",
  "role": "USER"
}
}

Errors

  • 400 Bad Request: identifier or password is missing or is not a string.
  • 401 Unauthorized: user does not exist, is inactive, or the password is incorrect. Message: Invalid credentials.
POST/auth/refresh

Refresh tokens

Exchanges a valid refresh token for a new access-token and refresh-token pair. Rotation invalidates the previous token version. No bearer token is required.

Request

{
  "refreshToken": "<jwt-refresh-token>"
}

Success response

{
  "accessToken": "<new-jwt-access-token>",
  "refreshToken": "<new-jwt-refresh-token>",
  "profileComplete": true,
  "message": "Profile complete.",
  "user": {
  "id": "clx123user",
  "username": "merchant-user",
  "email": "merchant@example.com",
  "role": "USER"
}
}

Errors

  • 400 Bad Request: refreshToken is missing or is not a string.
  • 401 Unauthorized: token is missing, malformed, expired, signed with the wrong secret, belongs to a missing or inactive user, or has an old token version. Message: Invalid refresh token.
POST/auth/logout

Logout

Invalidates the current user's refresh-token version. Existing access tokens remain subject to normal JWT expiry, but refresh attempts using the old version fail.

Request

Authorization: Bearer <access_token>

Request body: none

Success response

{
  "message": "Logged out successfully"
}

Errors

  • 401 Unauthorized: bearer token is missing, expired, revoked, or invalid.

Password reset

Password recovery does not require a bearer token. A six-digit OTP is sent by email, stored only as a SHA-256 hash, expires after one hour, and is single-use.

POST/auth/forgot-password

Request a password reset

Requests a password reset email for an active account. No bearer token is required.

Request

{
  "email": "merchant@example.com"
}

The endpoint intentionally returns the same 201 response whether or not the email belongs to an account, preventing account enumeration. A new request invalidates previous reset OTPs. The six-digit OTP is sent only through the configured SMTP service and is never returned in the HTTP response or stored in plaintext.

Success response

{
  "message": "If an account exists for that email, a password reset token has been sent."
}

Errors

  • 400 Bad Request: email is missing or is not a valid email address.
  • 500 Internal Server Error: email delivery failed or the email service is unavailable. The generated OTP is removed when delivery fails.
POST/auth/reset-password

Reset a password

Consumes the six-digit OTP received by email and sets a new password. No bearer token is required.

Request

{
  "otp": "482913",
  "newPassword": "new-strong-password"
}

The OTP is single-use. After a successful reset, refreshTokenVersion is incremented so all existing refresh tokens are invalidated. The user must log in again to receive a new access and refresh token pair.

Success response

{
  "message": "Password reset successfully. Please log in with your new password."
}

Errors

  • 400 Bad Request: OTP is missing, is not a string of exactly six digits, or newPassword is shorter than 6 characters.
  • 401 Unauthorized: OTP is invalid, expired, already used, or belongs to an inactive account. Message: Invalid or expired password reset OTP.

Token and authorization details

JWT claims

{
  "sub": "clx123user",
  "username": "merchant-user",
  "email": "merchant@example.com",
  "role": "USER",
  "version": 0
}

Treat access and refresh tokens as opaque strings. Do not construct or modify JWT claims.

Token invalidation

  • Refresh: increments refreshTokenVersion and returns a new pair.
  • Logout: increments refreshTokenVersion without issuing a new pair.
  • JWT strategy: rejects inactive users and tokens whose version no longer matches the database.

Security notes

  • Passwords are stored as bcrypt hashes and are never returned.
  • Login uses the same Invalid credentials message for a missing user, inactive user, or incorrect password.
  • Store refresh tokens securely and send them only to /auth/refresh.
  • Do not log or expose access tokens, refresh tokens, passwords, or API secrets in client-visible diagnostics.

Security essentials

Keep credentials, API traffic, and webhook integrations protected throughout the authentication lifecycle.

Transport

Use HTTPS for every API and dashboard request. Never send credentials over plain HTTP.

Credential management

Keep API keys and secrets in environment variables or a vault, rotate them regularly, and revoke compromised credentials.

Webhooks and access

Validate webhook signatures, restrict sensitive integrations by IP where possible, and review audit activity.

Related business authentication

Authenticate a business integration

This route authenticates a business integration rather than a platform user. It is implemented by SettlementController, outside the /auth controller. It requires a platform JWT whose email matches the onboarding participant owning the API key.

POST/settlement/authenticate

Authenticate a business integration

Verifies a business API key and API secret and creates a settlement session.

Request

Authorization: Bearer <access_token>
Content-Type: application/json

{
  "apiKey": "pk_live_abc123",
  "apiSecret": "sk_live_xyz789"
}

Use the returned settlement session token in the x-settlement-session header. The documented default expiry is 3600 seconds; exact expiry is controlled by settlement configuration.

Success response

{
  "success": true,
  "token": "session_1725900000000_0123456789abcdef",
  "expiresIn": 3600,
  "tokenType": "Bearer",
  "business": {
    "id": "participant-123",
    "businessName": "Fresh Store Ltd",
    "participantType": "RETAILER",
    "status": "ACTIVE"
  }
}

Errors

  • 400 Bad Request: apiKey or apiSecret is missing, empty, or not a string.
  • 401 Unauthorized: platform JWT is missing or invalid, credentials belong to another token owner (INVALID_TOKEN_FOR_API_KEYS), or the API secret is incorrect (INVALID_CREDENTIALS).
  • 404 Not Found: no active integration exists for the API key (BUSINESS_NOT_FOUND).
  • 403 Forbidden: business participant status is not ACTIVE (BUSINESS_NOT_ACTIVE).

Error example

{
  "statusCode": 401,
  "message": "Invalid API credentials",
  "error": "INVALID_CREDENTIALS"
}