TenderCheck AI uses a hybrid authentication strategy: the server sets an HttpOnlyDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/elecodes/TenderCheck-AI/llms.txt
Use this file to discover all available pages before exploring further.
token cookie as the primary session mechanism, and the same JWT is also returned in the response body so clients can store it as a Bearer token fallback. This dual approach exists because HttpOnly cookie reliability varies across platforms and CORS setups — native apps, server-to-server calls, and some cross-origin browser configurations cannot use cookies reliably, so the Authorization header path ensures those environments still work without any special server configuration.
The authMiddleware always checks the cookie first. If no cookie is present it looks for an Authorization: Bearer <token> header. Either path leads to the same verified JWT payload.
Register
Create a new TenderCheck AI account.Request Body
Full display name. Minimum 2 characters.
A valid email address. Stored in lowercase.
Must satisfy all of the following rules (validated server-side via Zod):
- Minimum 8 characters (
PASSWORD_MIN_LENGTH = 8) - At least one uppercase letter (A–Z)
- At least one number (0–9)
- At least one special character (any non-alphanumeric character)
Optional company or organisation name.
Success Response — 201 Created
The server auto-logs the user in after registration: it issues a JWT, sets the HttpOnly cookie, and returns the token in the body.
Confirmation string:
"User registered successfully".Signed JWT. Store in
localStorage as auth_token for the Bearer token fallback.The newly created user record.
token HttpOnly cookie is also set on the response automatically — no extra step needed in a browser context.
Error Responses
| Status | Cause |
|---|---|
400 | Zod validation failure (password rules, invalid email, name too short) |
500 | Email is already registered (duplicate account) |
When a duplicate email is submitted,
AuthService throws a plain Error("User already exists"). This is caught by the global error handler and returned as an HTTP 500 with the message "Something went wrong". User-facing code should treat any non-201 response from this endpoint as a registration failure and prompt the user to try a different email or log in instead.Login
Authenticate an existing user with email and password.This endpoint is rate-limited to 300 requests per 60-second window. Exceeding the limit returns HTTP
429. See Rate Limiting for details.Request Body
The account email address.
The account password.
When
true, the token cookie is set with maxAge = 30 days. When false or omitted, the cookie is a session cookie that is cleared when the browser is closed.Success Response — 200 OK
token cookie is set on the response in addition to the token field in the body.
Error Responses
| Status | Cause |
|---|---|
400 | Invalid email or password format (Zod validation) |
429 | Rate limit exceeded |
500 | Incorrect credentials (email not found or password mismatch) |
Incorrect credentials cause
AuthService.login to throw a plain Error("Invalid credentials"), which the global error handler returns as HTTP 500 with "Something went wrong". This is an internal design detail — production error handling maps all unrecognised errors to a safe 500 to prevent credential enumeration.Making Authenticated Requests
For protected endpoints, the frontend sends both the HttpOnly cookie and theAuthorization header on every request. The backend accepts whichever is present.
authMiddleware resolves the token:
- Checks
req.cookies.token(HttpOnly cookie path). - If no cookie, checks
req.headers.authorizationfor aBearer <token>string. - If neither is present, responds with HTTP 401
"No authorization token provided". - If a token is found but is invalid or expired, responds with HTTP 401
"Invalid or expired token".
Get Current User
Retrieve the authenticated user’s profile. Use this on page load to restore an existing session without requiring the user to log in again.Authorization: Bearer <token> header.
Success Response — 200 OK
The JWT payload decoded from the active token. Contains
userId (the only claim signed into the token by AuthService).The token currently stored in the HttpOnly cookie. Returned so the client can sync
localStorage if it was cleared (e.g., after a hard refresh).The
user object returned by /api/auth/me contains only the claims that were signed into the JWT. AuthService signs tokens with { userId: user.id } only — email and name are not included in the JWT payload and are therefore not present in this response. To get the full user profile (including name and email), use the data returned from /api/auth/login or /api/auth/register and cache it client-side.Error Responses
| Status | Cause |
|---|---|
401 | No token or invalid/expired token |
Logout
Clear the server-side session cookie.Success Response — 200 OK
token HttpOnly cookie is cleared with clearCookie. The frontend should also remove the localStorage token immediately after a successful logout:
Google OAuth (PKCE)
Sign in or register using a Google account via the Authorization Code + PKCE flow.This endpoint is rate-limited to 300 requests per 60-second window — the same limiter as
/api/auth/login. See Rate Limiting for details.Flow
Initiate on the frontend
The frontend generates a PKCE
codeVerifier and codeChallenge pair, then redirects the user to Google’s authorisation endpoint with response_type=code and the code_challenge.Google redirects back
After the user consents, Google redirects back to the frontend with a one-time
code in the URL query string.Send code + verifier to backend
The frontend POSTs the
code and the original codeVerifier (never exposed to Google) to /api/auth/google/callback.Request Body
The one-time authorization code returned by Google’s OAuth consent screen.
The PKCE code verifier generated by the frontend before initiating the OAuth flow. Never sent to Google directly — only to this endpoint.
Success Response — 200 OK
rememberMe = true (30-day maxAge) for Google logins.
Error Responses
| Status | Cause |
|---|---|
400 | Missing or malformed code / codeVerifier fields |
400 | Google account does not have an associated email |
401 | Google code exchange failed (expired or already used code) |
429 | Rate limit exceeded |
500 | GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET not configured on the server |
Required Backend Environment Variables
Why PKCE? The PKCE (Proof Key for Code Exchange) extension prevents authorization code interception attacks. The
codeVerifier is a cryptographic secret known only to the initiating client — even if an attacker intercepts the code in the redirect URL, they cannot exchange it without the matching verifier. This is why TenderCheck AI uses the Authorization Code + PKCE flow rather than the legacy Implicit flow (which exposed access tokens directly in the URL).Password Reset
Request a password reset link for an account.Request Body
The email address associated with the account.
Success Response — 200 OK
This endpoint always returns HTTP 200, regardless of whether the email is registered. This is intentional — it prevents user enumeration attacks where an attacker could determine which email addresses have accounts by probing the API.
Error Responses
| Status | Cause |
|---|---|
400 | Invalid email format |
JWT Token Details
All JWTs are signed and verified using theJWT_SECRET environment variable.
| Property | Value |
|---|---|
| Signing algorithm | HS256 (jsonwebtoken default) |
| Default expiry | 1d (1 day, from AuthService) |
Session cookie (no rememberMe) | Cookie cleared on browser close; token itself valid for 1 day |
Persistent cookie (rememberMe: true) | Cookie maxAge = 30 days |
| JWT payload | { userId: string } |
userId claim:
authMiddleware attaches the decoded payload to req.user with the following TypeScript shape — note that email and role will only be populated if they were signed into the token by a future version of the service: