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 200
{ "status": "password changed" }
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 |
Active Sessions
These endpoints let a user inspect and revoke their own signed-in devices — typically wired to a "Security" or "Active sessions" panel in your tenant portal. No admin role or API key required; the user authenticates with their own access token.
List my sessions
GET /api/v1/me/sessions
Returns one row per device the user is currently signed in on. The row matching the access token making the request is flagged with current: true so the UI can render "This device" and avoid surprising the user when they revoke.
Response
{
"sessions": [
{
"id": "0193d07e-d8cb-7819-b0a1-48c90e602461",
"family_id": "0193d07e-aaaa-7819-b0a1-48c90e602461",
"current": true,
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
"browser": "Chrome",
"browser_version": "125",
"os": "macOS",
"os_version": "14.5",
"device_type": "desktop",
"location": {
"city": "Salt Lake City",
"region": "UT",
"country": "US"
},
"started_at": "2026-05-01T12:00:00Z",
"last_active_at": "2026-05-09T08:30:00Z",
"expires_at": "2026-05-31T12:00:00Z"
}
],
"count": 1
}
| Field | Type | Notes |
|-------|------|-------|
| id | string | Pass to the revoke endpoints below |
| current | boolean | Only set on the row the caller is signed in from |
| location | object \| undefined | Omitted entirely when no geo data is available |
| device_type | string \| null | One of desktop, mobile, tablet, bot |
The current flag depends on the access token carrying a sid claim. Tokens issued before the sessions feature was deployed (or via flows that don't issue refresh tokens, such as the dashboard-only login) lack sid — in those cases no row will be flagged. The list is still complete; the UI just won't be able to highlight "this device."
Revoke one session
DELETE /api/v1/me/sessions/{id}
Signs the user out of one specific device. If the id matches the caller's current session, the next refresh will return 401 and the SDK will redirect to login — that's intentional ("sign out from this device" is a valid action).
Response
{ "status": "revoked" }
| Status | Description |
|--------|-------------|
| 404 | Session id doesn't exist or doesn't belong to the calling user |
Revoke all OTHER sessions
DELETE /api/v1/me/sessions
Signs the user out of every device EXCEPT the one making the request. Common UX: a "Sign out everywhere else" button in account security settings, after a password change.
Response
{
"revoked": 4,
"kept": 1
}
kept is 1 when the caller had a sid claim and that session still exists, 0 otherwise (in which case ALL sessions were revoked, including the caller's — this matters for legacy tokens; document it in your UI).
OAuth Consents
List consents
GET /api/v1/auth/consents
Returns all active OAuth consent grants for the authenticated user.
Response
{
"consents": [
{
"id": "consent_01j...",
"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 200
{ "status": "consent revoked" }
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
{
"user_id": "usr_01j...",
"access_token": "eyJhbGci...",
"refresh_token": "rt_01j...",
"expires_in": 900,
"email_verified": true
}
| Field | Type | Description |
|-------|------|-------------|
| user_id | string | Stable user identifier |
| 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) |
| email_verified | boolean | Whether the user's email has been verified |
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.