User API
Self-service endpoints for authenticated users to manage their own profile, credentials, and OAuth consents.
All endpoints require Authorization: Bearer <access_token>. The token is the user's own JWT — no admin role needed.
You may include the optional X-Bulwark-App-Id header to scope user activity to a specific application context.
Profile
Get profile
GET /api/v1/user/profile
Returns the authenticated user's profile.
Response
{
"id": "usr_01j...",
"email": "[email protected]",
"email_verified": true,
"display_name": "Jane Doe",
"avatar_url": "https://cdn.bulwarkauth.com/avatars/usr_01j....png",
"roles": ["user"],
"created_at": "2026-01-15T09:00:00Z"
}
| Field | Type | Description |
|-------|------|-------------|
| id | string | Stable user identifier |
| email | string | User's email address |
| email_verified | boolean | Whether the email has been verified |
| display_name | string \| null | User's display name |
| avatar_url | string \| null | URL to profile avatar |
| roles | string[] | Roles assigned to this user |
| created_at | string | ISO 8601 creation timestamp |
Update profile
PATCH /api/v1/user/profile
Updates mutable profile fields. Only include fields you want to change.
Request body
| Field | Type | Description |
|-------|------|-------------|
| display_name | string | New display name |
| avatar_url | string | New avatar URL |
{
"display_name": "Jane D.",
"avatar_url": "https://example.com/my-avatar.png"
}
Response — updated profile object (same shape as GET).
Password
Change password
POST /api/v1/user/change-password
Changes the authenticated user's password. Requires the current password for verification.
Request body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| current_password | string | Yes | The user's current password |
| new_password | string | Yes | The new password |
Password requirements: 8–128 characters. No other constraints are enforced by default; configure policy in the dashboard.
{
"current_password": "hunter2",
"new_password": "correct-horse-battery-staple"
}
Response — 204 No Content on success.
Errors
| Status | Code | Description |
|--------|------|-------------|
| 400 | password_too_short | New password is fewer than 8 characters |
| 400 | password_too_long | New password exceeds 128 characters |
| 401 | invalid_credentials | current_password is incorrect |
OAuth Consents
List consents
GET /api/v1/auth/consents
Returns all active OAuth consent grants for the authenticated user.
Response
[
{
"id": "consent_01j...",
"client_name": "Acme Dashboard",
"client_id": "client_01j...",
"scopes": ["read:profile", "read:email"],
"granted_at": "2026-02-10T14:30:00Z"
}
]
Revoke consent
DELETE /api/v1/auth/consents/{id}
Revokes an OAuth consent grant. The associated OAuth application will lose access on the next token refresh.
Response — 204 No Content
Token refresh
Refresh access token
POST /api/v1/auth/refresh
Exchanges a refresh token for a new access token and refresh token. The old refresh token is invalidated after this call (token rotation).
Request body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| refresh_token | string | Yes | A valid refresh token |
{
"refresh_token": "rt_01j..."
}
Response
{
"access_token": "eyJhbGci...",
"refresh_token": "rt_01j...",
"expires_in": 900,
"token_type": "Bearer"
}
| Field | Type | Description |
|-------|------|-------------|
| access_token | string | New JWT access token |
| refresh_token | string | New refresh token (old one is invalidated) |
| expires_in | integer | Access token lifetime in seconds (default: 900) |
| token_type | string | Always "Bearer" |
Errors
| Status | Code | Description |
|--------|------|-------------|
| 401 | invalid_refresh_token | Token is expired, revoked, or malformed |
| 401 | refresh_token_rotated | Token was already used (possible replay attack) |
Note: The SDKs handle token refresh automatically. Call this endpoint directly only if you are managing tokens without an SDK.