Skip to content

API reference

The full user-service profile exposes the following versioned endpoints. Protected endpoints require a JWT with the relevant role or permission. The Administrator role is a superuser for the included management policies. The minimal profile exposes only the health and metrics endpoints.

Set a shell variable for the examples:

export API_URL=http://localhost:5070

Health

GET /api/v1/health is anonymous and checks database connectivity in the full profile.

Deployment probes are also available: GET /health/live checks that the process can serve requests without dependencies, while GET /health/ready checks database connectivity and returns 503 until the full profile is ready. The legacy /api/v1/health endpoint remains available for clients.

curl --fail "$API_URL/api/v1/health"

Successful full-profile response:

{"status":"Healthy","db":"OK","timestamp":"2026-01-01T00:00:00Z"}

The minimal profile returns the same status and timestamp fields without the db field.

Authentication

POST /api/v1/auth/generate-jwt is anonymous. Create an administrator first with dotnet run -- --create-admin, then exchange the credentials for a JWT:

curl --fail "$API_URL/api/v1/auth/generate-jwt" \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@example.com","password":"change-this-password"}'

Successful response:

{
  "token": "eyJ...",
  "user": {
    "firstName": "Administrator",
    "email": "admin@example.com",
    "roles": ["Administrator"]
  }
}

Invalid credentials return 401 Unauthorized.

Access tokens expire after JWT_EXPIRY seconds (300 by default). Login also returns a one-time refresh token valid for JWT_REFRESH_EXPIRY seconds (7 days by default). Refresh tokens are stored as hashes and rotated on every refresh; reusing a rotated or revoked token returns 401 Unauthorized.

curl --fail "$API_URL/api/v1/auth/refresh" \
  -H 'Content-Type: application/json' \
  -d '{"refreshToken":"REFRESH_TOKEN"}'

Revoke the current access token and all refresh tokens for its account:

curl --fail -X POST "$API_URL/api/v1/auth/revoke" \
  -H "Authorization: Bearer $TOKEN"

Optional email actions are available in the full profile. Password reset requests return 202 Accepted without revealing whether an account exists:

curl --fail -X POST "$API_URL/api/v1/auth/password-reset/request" \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com"}'

Confirm with { "token": "...", "newPassword": "..." } at /api/v1/auth/password-reset/confirm. Authenticated users can request email verification at /api/v1/auth/email-verification/request; confirm with { "token": "..." } at /api/v1/auth/email-verification/confirm. Tokens are one-time, hashed at rest, and only delivered when an IEmailSender implementation and EMAIL_ACTION_BASE_URL are configured.

Passwords must be at least 12 characters and contain uppercase, lowercase, numeric, and special characters. After AUTH_MAX_FAILED_ATTEMPTS failed logins (5 by default), the account is locked for AUTH_LOCKOUT_MINUTES (15 by default). Failed-login responses intentionally do not reveal whether an email exists or whether an account is locked.

Use the token for protected requests. Collection endpoints return a stable response envelope and support page (default 1), pageSize (default 25, maximum 100), q for case-insensitive text search, and includeDeleted for soft-deletable resources:

export TOKEN='eyJ...'
curl --fail "$API_URL/api/v1/users" \
  -H "Authorization: Bearer $TOKEN"

Example response:

{
  "items": [],
  "page": 1,
  "pageSize": 25,
  "totalCount": 0,
  "totalPages": 0
}

Invalid paging values return 400 Bad Request as application/problem+json. Single-resource lookups remain available with ?id=<resource-id> and return the resource directly; update and delete routes use /api/v1/<resource>/{id}.

Authorization policies

The full profile issues permission claims from a user's assigned roles and evaluates these policies at the controller boundary:

Policy Required permission Protected resource
users.manage users.manage /api/v1/users
roles.manage roles.manage /api/v1/roles
permissions.manage permissions.manage /api/v1/permissions
role-permissions.manage role-permissions.manage /api/v1/role-permissions

Create the permission records and role-permission links through the included RBAC endpoints, then issue a new JWT for the changes to take effect. Existing tokens do not change until they expire or are reissued.

Users

All user endpoints require the users.manage policy.

Method Endpoint Purpose
GET /api/v1/users List users
POST /api/v1/users Create a user; the password is hashed before storage
PUT /api/v1/users/{id} Update a user
DELETE /api/v1/users/{id} Delete a user

Example create request:

curl --fail "$API_URL/api/v1/users" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "firstName":"Jane",
    "lastName":"Doe",
    "email":"jane@example.com",
    "phone":254700000000,
    "password":"use-a-secret-manager"
  }'

Roles

All role endpoints require the roles.manage policy.

Method Endpoint Purpose
GET /api/v1/roles List roles
POST /api/v1/roles Create a role
PUT /api/v1/roles/{id} Update a role
DELETE /api/v1/roles/{id} Delete a role
curl --fail "$API_URL/api/v1/roles" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Support","description":"Support team access"}'

Permissions

All permission endpoints require the permissions.manage policy.

Method Endpoint Purpose
GET /api/v1/permissions List permissions
POST /api/v1/permissions Create a permission
PUT /api/v1/permissions/{id} Update a permission
DELETE /api/v1/permissions/{id} Delete a permission
curl --fail "$API_URL/api/v1/permissions" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"users.read","description":"View users"}'

Role permissions

All role-permission endpoints require the role-permissions.manage policy. roleId and permissionId are GUIDs returned by the roles and permissions endpoints.

Method Endpoint Purpose
GET /api/v1/role-permissions List role-permission links
POST /api/v1/role-permissions Grant a permission to a role
PUT /api/v1/role-permissions/{id} Update a link
DELETE /api/v1/role-permissions/{id} Revoke a link
curl --fail "$API_URL/api/v1/role-permissions" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"roleId":"ROLE_GUID","permissionId":"PERMISSION_GUID"}'

Metrics and tracing

GET /metrics is anonymous and returns Prometheus-compatible metrics. API responses include an X-Trace-Id header for correlation with structured logs and distributed traces.