All client requests go through the API Gateway at http://localhost:4000.
User Service and Notification Service are internal-only and not meant to be
called directly by clients (they reject any request without the internal
shared-secret header — see ARCHITECTURE.md).
Creates a new user account. Publishes a user.registered event internally
(the Notification Service will asynchronously send a welcome notification).
Request body
{
"name": "Jane Doe",
"email": "jane@example.com",
"password": "a-strong-password"
}Success — 201
{
"user": {
"id": "uuid",
"name": "Jane Doe",
"email": "jane@example.com",
"createdAt": "2026-08-19T10:00:00.000Z"
}
}Errors
400— validation failed ({ "errors": ["..."] })409— email already registered
Authenticates a user and returns a JWT. Publishes a user.login event
internally (the Notification Service will asynchronously send a login alert).
Request body
{
"email": "jane@example.com",
"password": "a-strong-password"
}Success — 200
{
"token": "eyJhbGciOi...",
"expiresIn": "2h"
}Errors
400— validation failed401— invalid email or password
Send the JWT from login as: Authorization: Bearer <token>
Returns the profile of the currently authenticated user.
Success — 200
{
"user": {
"id": "uuid",
"name": "Jane Doe",
"email": "jane@example.com",
"createdAt": "2026-08-19T10:00:00.000Z"
}
}Errors
401— missing/invalid/expired token
Returns the notification history generated for the authenticated user
(populated asynchronously via NATS events — see ARCHITECTURE.md).
Success — 200
{
"userId": "uuid",
"notifications": [
{
"type": "WELCOME_EMAIL",
"channel": "email",
"to": "jane@example.com",
"subject": "Welcome, Jane Doe!",
"body": "Hi Jane Doe, thanks for signing up.",
"sentAt": "2026-08-19T10:00:00.100Z"
},
{
"type": "LOGIN_ALERT",
"channel": "email",
"to": "jane@example.com",
"subject": "New login to your account",
"body": "We noticed a new login at 2026-08-19T10:05:00.000Z.",
"sentAt": "2026-08-19T10:05:00.050Z"
}
]
}Errors
401— missing/invalid/expired token
Each service exposes GET /health (no auth required) for readiness/liveness
probes:
GET http://localhost:4000/health— API GatewayGET http://localhost:4001/health— User Service (internal)GET http://localhost:4002/health— Notification Service (internal)
These exist so the Gateway can forward requests, and require the
x-internal-api-key header to match the shared secret:
POST /users/register(User Service, port 4001)POST /users/login(User Service, port 4001)GET /users/:id(User Service, port 4001)GET /notifications/:userId(Notification Service, port 4002)