User Profile

Manage the authenticated user's profile, including personal information, preferences, and account settings.


Get Current User

Retrieve the authenticated user's profile with organization and membership details.

Endpoint: GET /api/v1/me

Authentication: Required (Bearer token)

Request

curl https://api.storno.ro/api/v1/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

Response (200 OK)

Returns flat JSON with user profile, organization, memberships, and subscription information.

{
  "id": "01HQZX1234ABCDEF5678WXYZ",
  "email": "[email protected]",
  "firstName": "John",
  "lastName": "Doe",
  "phone": "+40721234567",
  "timezone": "Europe/Bucharest",
  "respectQuietHours": true,
  "emailConfirmed": true,
  "createdAt": "2026-01-15T10:30:00Z",
  "updatedAt": "2026-02-16T09:15:00Z",
  "preferences": {
    "language": "ro",
    "theme": "light",
    "notifications": {
      "email": true,
      "push": false
    }
  },
  "organization": {
    "id": "01HQZY5678GHIJKL9012STUV",
    "name": "My Company SRL",
    "slug": "my-company-srl",
    "createdAt": "2026-01-15T10:30:00Z"
  },
  "memberships": [
    {
      "id": "01HR0Z9012MNOPQR3456UVWX",
      "organizationId": "01HQZY5678GHIJKL9012STUV",
      "organizationName": "My Company SRL",
      "role": "owner",
      "joinedAt": "2026-01-15T10:30:00Z"
    }
  ],
  "subscription": {
    "plan": "pro",
    "status": "active",
    "expiresAt": "2027-01-15T10:30:00Z",
    "features": {
      "maxInvoices": 1000,
      "maxCompanies": 5,
      "apiAccess": true
    }
  }
}

Response Fields

FieldTypeDescription
idstringUser's unique identifier (ULID)
emailstringUser's email address
firstNamestringUser's first name
lastNamestringUser's last name
phonestringUser's phone number
timezonestringUser's timezone (IANA format)
respectQuietHoursbooleanWhen true, push notifications are skipped between 22:00 and 08:00 in the user's timezone
emailConfirmedbooleanWhether email has been confirmed
createdAtstringAccount creation timestamp (ISO 8601)
updatedAtstringLast profile update timestamp (ISO 8601)
preferencesobjectUser preferences and settings
organizationobjectCurrent/default organization details
membershipsarrayAll organization memberships
subscriptionobjectSubscription plan and features

Update User Profile

Update the authenticated user's profile information.

Endpoint: PATCH /api/v1/me

Authentication: Required (Bearer token)

Body Parameters

ParameterTypeRequiredDescription
firstNamestringNoUser's first name
lastNamestringNoUser's last name
phonestringNoPhone number (E.164 format recommended)
timezonestringNoTimezone (IANA format, e.g., "Europe/Bucharest")
respectQuietHoursbooleanNoMute push notifications during quiet hours (22:00–08:00 user-local)
preferencesobjectNoUser preferences object
passwordstringNoNew password (requires currentPassword)
currentPasswordstringNoCurrent password (required when changing password)

Request

curl -X PATCH https://api.storno.ro/api/v1/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "John",
    "lastName": "Smith",
    "phone": "+40721234567",
    "timezone": "Europe/Bucharest",
    "preferences": {
      "language": "ro",
      "theme": "dark",
      "notifications": {
        "email": true,
        "push": true
      }
    }
  }'

Change Password Example

const response = await fetch('https://api.storno.ro/api/v1/me', {
  method: 'PATCH',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    currentPassword: 'OldPassword123!',
    password: 'NewSecurePassword456!',
  }),
});

Response (200 OK)

Returns the updated user profile in the same format as GET /api/v1/me.

{
  "id": "01HQZX1234ABCDEF5678WXYZ",
  "email": "[email protected]",
  "firstName": "John",
  "lastName": "Smith",
  "phone": "+40721234567",
  "timezone": "Europe/Bucharest",
  "respectQuietHours": true,
  "emailConfirmed": true,
  "createdAt": "2026-01-15T10:30:00Z",
  "updatedAt": "2026-02-16T10:45:00Z",
  "preferences": {
    "language": "ro",
    "theme": "dark",
    "notifications": {
      "email": true,
      "push": true
    }
  },
  "organization": { ... },
  "memberships": [ ... ],
  "subscription": { ... }
}

Error Codes

CodeDescription
400Bad Request - Invalid parameters or validation failed
401Unauthorized - Invalid or expired token
403Forbidden - Incorrect current password
422Unprocessable Entity - Invalid timezone or phone format

Error Response Examples

Validation Error (400)

{
  "code": 400,
  "message": "Validation failed",
  "errors": {
    "phone": ["Phone number must be in E.164 format."],
    "timezone": ["Invalid timezone identifier."]
  }
}

Incorrect Current Password (403)

{
  "code": 403,
  "message": "Current password is incorrect."
}

Delete Account

Permanently delete the user's account. This action soft-deletes the account and anonymizes personally identifiable information (PII).

Endpoint: DELETE /api/v1/me

Authentication: Required (Bearer token)

Body Parameters

ParameterTypeRequiredDescription
passwordstringYesCurrent password for confirmation

Request

curl -X DELETE https://api.storno.ro/api/v1/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "password": "UserPassword123!"
  }'

Response (204 No Content)

No response body. The account is successfully deleted and all tokens are invalidated.

What Gets Deleted

When you delete your account:

  1. User record is soft-deleted (marked as deleted, not physically removed)
  2. Personal information is anonymized:
    • Email replaced with deleted-{timestamp}@example.com
    • First and last name removed
    • Phone number removed
    • Profile picture removed
  3. Organization ownership is transferred to the next admin (if any)
  4. Memberships are removed from all organizations
  5. Sessions and tokens are immediately revoked
  6. Invoices and financial data are preserved for legal/tax compliance (anonymized)

Error Codes

CodeDescription
400Bad Request - Missing password
401Unauthorized - Invalid or expired token
403Forbidden - Incorrect password
409Conflict - Cannot delete account with active subscriptions or pending invoices

Error Response Examples

Incorrect Password (403)

{
  "code": 403,
  "message": "Password is incorrect. Account not deleted."
}

Active Subscription (409)

{
  "code": 409,
  "message": "Cannot delete account with active subscription. Please cancel your subscription first."
}

Usage Notes

Preferences Object

The preferences object is flexible and can contain any custom user settings:

{
  "language": "ro",
  "theme": "dark",
  "currency": "RON",
  "dateFormat": "d/m/Y",
  "notifications": {
    "email": true,
    "push": false,
    "sms": false,
    "invoiceReminders": true
  },
  "dashboard": {
    "defaultView": "grid",
    "showStats": true
  }
}

Timezone Support

Valid timezone values follow the IANA Time Zone Database:

  • Europe/Bucharest (Romania)
  • Europe/London (UK)
  • America/New_York (US Eastern)
  • Asia/Tokyo (Japan)
  • etc.

Phone Number Format

While any format is accepted, E.164 format is recommended for international compatibility:

  • E.164 format: +40721234567
  • Alternative: 0721234567 (national format)

Security Best Practices

  1. Password changes require the current password
  2. Account deletion requires password confirmation
  3. Email changes may require re-confirmation (check with support)
  4. Token invalidation occurs on password change (user must re-login)
  5. Audit logging tracks all profile changes for security

Rate Limiting

  • Profile updates: 10 requests per minute
  • Account deletion: 3 attempts per hour (failed password attempts)