This document covers only the REST API endpoints exposed by UBS — not the whole application internals.
| What | URL |
|---|---|
| Swagger UI (try endpoints in the browser) | http://localhost:8081/swagger-ui/index.html |
| OpenAPI JSON (import into Postman, etc.) | http://localhost:8081/v3/api-docs |
| Health check (no auth) | http://localhost:8081/actuator/health |
Default port is 8081 (
server.portinapplication.properties). Change examples if you use another port.
- PostgreSQL must be running and match
spring.datasource.*inapplication.properties(orapplication-local.properties). - Start the app:
mvn spring-boot:run
- Wait until you see something like
Started JavaTApplication(main class name) in the console. - Open Swagger UI in your browser.
| Field | Value |
|---|---|
| Name | Emmanuel Uwimana |
admin@ubs.rw |
|
| Password | Admin@1234 |
| Role | ADMIN |
| Area | Status | Notes |
|---|---|---|
| User registration | Done | BCrypt password; no JWT until verified |
| Login + JWT | Done | Access token (typ=access) + refresh token (typ=refresh) |
| RBAC | Done | USER, MODERATOR, MANAGER, ADMIN |
| Logout | Done | Blacklists refresh token jti in revoked_tokens |
| Refresh tokens | Done | POST /auth/refresh with refreshToken body |
| Email verification | Done | Link (GET /verify-email) + OTP (POST /otp/verify) |
| OTP | Done | 6-digit code, 10 min expiry, stored in otp_codes |
| Forgot / reset password | Done | Link + OTP; validate token via GET /reset-password |
| DTO validation | Done | @Valid on all request bodies |
| Global errors | Done | GlobalExceptionHandler → consistent ApiResponse + errors[] |
| # | Method | Path | Purpose |
|---|---|---|---|
| 1 | POST | /api/v1/auth/register |
Create user (PENDING until verified) |
| 2 | GET | /api/v1/auth/verify-email?token= |
Verify via email link |
| 3 | POST | /api/v1/auth/resend-verification |
Resend link + OTP |
| 4 | POST | /api/v1/auth/otp/send |
Request OTP (EMAIL_VERIFICATION or PASSWORD_RESET) |
| 5 | POST | /api/v1/auth/otp/verify |
Verify email with 6-digit OTP |
| 6 | POST | /api/v1/auth/login |
Get accessToken + refreshToken |
| 7 | POST | /api/v1/auth/refresh |
Exchange refresh token for new access token |
| 8 | POST | /api/v1/auth/logout |
Blacklist refresh token (send access JWT in header) |
| 9 | POST | /api/v1/auth/forgot-password |
Send reset link + OTP |
| 10 | GET | /api/v1/auth/reset-password?token= |
Check if reset token is valid |
| 11 | POST | /api/v1/auth/reset-password |
Reset with link token |
| 12 | POST | /api/v1/auth/reset-password-otp |
Reset with OTP |
Authorize in Swagger with the access token (not refresh).
| # | Method | Path | Who |
|---|---|---|---|
| 13 | GET | /api/v1/users/me |
Any logged-in user |
| 14 | PATCH | /api/v1/users/me |
Any logged-in user |
| 15 | PATCH | /api/v1/users/me/password |
Any logged-in user |
| 16 | POST | /api/v1/users |
ADMIN — create user |
| 17 | GET | /api/v1/users |
ADMIN or MANAGER — list users |
| 18 | GET | /api/v1/users/{id} |
ADMIN or MANAGER — read user |
| 19 | PATCH | /api/v1/users/{id} |
ADMIN — update profile |
| 20 | PATCH | /api/v1/users/{id}/role |
ADMIN — update role |
| 21 | DELETE | /api/v1/users/{id} |
ADMIN — soft-delete |
| 22 | DELETE | /api/v1/users/{id}/permanent |
ADMIN — hard delete |
| 23 | PATCH | /api/v1/users/{id}/deactivate |
ADMIN |
| 24 | PATCH | /api/v1/users/{id}/activate |
ADMIN |
- Expand Authentication → POST /api/v1/auth/login.
- Click Try it out.
- Request body:
{ "email": "admin@ubs.rw", "password": "Admin@1234" } - Click Execute.
- In the response, find
data.accessTokenand copy the long string (starts witheyJ...).
- Click the green Authorize button (top right).
- Under bearerAuth, paste only the
accessTokenvalue (do not typeBearer— Swagger adds it). - Click Authorize, then Close.
- Expand User Management → e.g. GET /api/v1/users/me.
- Try it out → Execute.
- Expect
200and your user indata.
Click Authorize → Logout to clear the token.
Every API returns the same envelope:
{
"success": true,
"message": "Login successful",
"data": { },
"timestamp": "2026-06-04T12:00:00Z",
"path": "/api/v1/auth/login"
}- success —
trueorfalse - data — payload on success (tokens, user, page of users, etc.)
- errors — list of field errors on validation failure (400)
- On 401 / 403,
successisfalseandmessageexplains why
Set the base URL once:
$base = "http://localhost:8081"$loginBody = @{
email = "admin@ubs.rw"
password = "Admin@1234"
} | ConvertTo-Json
$login = Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/login" `
-ContentType "application/json" -Body $loginBody
$token = $login.data.accessToken
Write-Host "Token:" $token.Substring(0, 20) "..."$headers = @{ Authorization = "Bearer $token" }
Invoke-RestMethod -Method GET -Uri "$base/api/v1/users/me" -Headers $headersInvoke-RestMethod -Method GET -Uri "$base/api/v1/users?page=0&size=10&sortBy=createdAt&sortDir=desc" `
-Headers $headersInvoke-RestMethod -Method GET -Uri "$base/api/v1/users/1" -Headers $headers$registerBody = @{
firstName = "Marie Claire"
lastName = "Ingabire"
username = "mclaire01"
email = "marie.ingabire@example.rw"
password = "Secret@123"
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/register" `
-ContentType "application/json" -Body $registerBodyNew users are PENDING until email is verified. Login returns 403 until you call verify-email.
# Replace YOUR_TOKEN with the token from the verification email or database
Invoke-RestMethod -Method GET -Uri "$base/api/v1/auth/verify-email?token=YOUR_TOKEN"$body = @{ email = "marie.ingabire@example.rw" } | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/resend-verification" `
-ContentType "application/json" -Body $body$body = @{ email = "admin@ubs.rw" } | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/forgot-password" `
-ContentType "application/json" -Body $body$body = @{ refreshToken = $login.data.refreshToken } | ConvertTo-Json
$refreshed = Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/refresh" `
-ContentType "application/json" -Body $body
$token = $refreshed.data.accessToken$body = @{ refreshToken = $login.data.refreshToken } | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/logout" `
-Headers @{ Authorization = "Bearer $token" } `
-ContentType "application/json" -Body $body# Send OTP for email verification
$body = @{ email = "marie.ingabire@example.rw"; purpose = "EMAIL_VERIFICATION" } | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/otp/send" `
-ContentType "application/json" -Body $body
# Verify (code from email or otp_codes table in dev)
$body = @{
email = "marie.ingabire@example.rw"
code = "123456"
purpose = "EMAIL_VERIFICATION"
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/otp/verify" `
-ContentType "application/json" -Body $bodyInvoke-RestMethod -Method GET -Uri "$base/api/v1/auth/reset-password?token=TOKEN_FROM_EMAIL"# Via link token
$body = @{
token = "TOKEN_FROM_EMAIL"
newPassword = "NewSecret@456"
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/reset-password" `
-ContentType "application/json" -Body $body
# Via OTP
$body = @{
email = "marie.ingabire@example.rw"
code = "123456"
newPassword = "NewSecret@456"
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/reset-password-otp" `
-ContentType "application/json" -Body $body$body = @{ firstName = "OnlyName" } | ConvertTo-Json
try {
Invoke-RestMethod -Method POST -Uri "$base/api/v1/auth/register" `
-ContentType "application/json" -Body $body
} catch {
$_.ErrorDetails.Message # JSON with success=false and errors[]
}$body = @{ firstName = "Claudine" } | ConvertTo-Json
Invoke-RestMethod -Method PATCH -Uri "$base/api/v1/users/me" `
-Headers $headers -ContentType "application/json" -Body $body$body = @{
currentPassword = "Admin@1234"
newPassword = "Admin@1234"
} | ConvertTo-Json
Invoke-RestMethod -Method PATCH -Uri "$base/api/v1/users/me/password" `
-Headers $headers -ContentType "application/json" -Body $body$body = @{
firstName = "Jean Paul"
lastName = "Mizero"
username = "jmizero"
email = "jean.mizero@example.rw"
password = "Secret@123"
role = "USER"
} | ConvertTo-Json
Invoke-RestMethod -Method POST -Uri "$base/api/v1/users" `
-Headers $headers -ContentType "application/json" -Body $bodyInvoke-RestMethod -Method DELETE -Uri "$base/api/v1/users/2" -Headers $headers$body = @{ role = "MODERATOR" } | ConvertTo-Json
Invoke-RestMethod -Method PATCH -Uri "$base/api/v1/users/2/role" `
-Headers $headers -ContentType "application/json" -Body $bodyInvoke-RestMethod -Method PATCH -Uri "$base/api/v1/users/2/deactivate" -Headers $headers
Invoke-RestMethod -Method PATCH -Uri "$base/api/v1/users/2/activate" -Headers $headersBASE=http://localhost:8081
# Login
TOKEN=$(curl -s -X POST "$BASE/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"admin@ubs.rw","password":"Admin@1234"}' \
| jq -r '.data.accessToken')
# My profile
curl -s "$BASE/api/v1/users/me" -H "Authorization: Bearer $TOKEN" | jq
# List users (admin)
curl -s "$BASE/api/v1/users?page=0&size=5" -H "Authorization: Bearer $TOKEN" | jq| Code | Meaning | Typical cause |
|---|---|---|
| 200 | OK | Success |
| 201 | Created | Register |
| 400 | Bad request | Validation errors |
| 401 | Unauthorized | Missing/invalid JWT or wrong login |
| 403 | Forbidden | Wrong role or email not verified |
| 404 | Not found | User id does not exist |
| 409 | Conflict | Duplicate email/username on register |
From the project root (app must already be running on port 8081):
# Quick smoke test (login + main routes)
.\scripts\test-api.ps1
# Full suite: register, verify, OTP, refresh, logout, reset, validation, CRUD (25 steps)
.\scripts\test-all-endpoints.ps1The full script reads verification/OTP tokens from PostgreSQL when email is not configured — requires psql on your PATH and DB at 127.0.0.1:5435.
Use this when you finish a testing session:
- App starts without errors (Flyway migrations OK)
- Swagger UI loads at
/swagger-ui/index.html -
POST /auth/registerdoes not return JWT until verified -
GET /auth/verify-emailorPOST /auth/otp/verifyactivates account -
POST /auth/loginreturnsaccessTokenandrefreshToken -
POST /auth/refreshreturns new access token -
POST /auth/logoutinvalidates refresh token - Invalid register body returns 400 with
errors[] - After Authorize,
GET /users/mereturns 200 -
GET /usersas admin returns paginated list - Without token,
GET /users/mereturns 401
| Problem | What to try |
|---|---|
| Connection refused | Is the app running? Is the port 8081? |
| Flyway / DB errors | Check PostgreSQL is up; URL/username/password in properties |
401 on /users/me |
Login again; use access token in Authorize (not refresh) |
| 403 on login | Email not verified — verify first or use admin@ubs.rw |
403 on /users |
Need ADMIN or MANAGER role for list/read |
| Refresh fails after logout | Expected — refresh token was blacklisted |
| Swagger 404 | Use /swagger-ui/index.html not only /swagger-ui.html |
| File | Role |
|---|---|
src/main/java/.../config/SwaggerConfig.java |
OpenAPI title, JWT scheme, testing hints |
src/main/java/.../auth/AuthController.java |
Auth endpoints + Swagger descriptions |
src/main/java/.../user/UserController.java |
User endpoints + Swagger descriptions |
src/main/resources/application.properties |
Port, public paths, SpringDoc settings |