Overview
The Profile Management API allows users to view and update their profile information, manage contact details, upload profile images, and control account settings including freeze, unfreeze, and deletion.
Get User Profile
Retrieve the authenticated user’s complete profile information.
Endpoint: GET /api/profile
Headers:
Authorization : Bearer <access_token>
Response: 200 OK
{
"code" : 200 ,
"message" : "Profile retrieved successfully" ,
"data" : {
"id" : 123 ,
"username" : "johndoe" ,
"name" : "John Doe" ,
"kyc_status" : "approved" ,
"is_active" : true ,
"is_frozen" : false ,
"meta_data" : "{ \" theme \" : \" dark \" }" ,
"phone" : {
"id" : 1 ,
"phone" : "1234567890" ,
"country_code" : "1" ,
"verified" : true
},
"email" : {
"id" : 1 ,
"email" : "john@example.com" ,
"verified" : true
},
"phones" : [
{
"id" : 1 ,
"phone" : "1234567890" ,
"country_code" : "1" ,
"verified" : true
}
],
"emails" : [
{
"id" : 1 ,
"email" : "john@example.com" ,
"verified" : true
}
],
"profile_image_url" : "https://storage.rcoinx.com/uploads/profile.jpg?expires=..."
}
}
Example:
JavaScript
TypeScript
Flutter
async function getProfile () {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/profile' , {
method: 'GET' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data . data ;
}
// Usage
try {
const profile = await getProfile ();
console . log ( 'User profile:' , profile );
} catch ( error ) {
console . error ( 'Failed to fetch profile:' , error );
}
The profile_image_url is a pre-signed URL with 24-hour expiration. You should refetch the profile to get a new URL after expiration.
Update Profile (JSON)
Update user profile information including username, name, and profile image reference.
Endpoint: PUT /api/profile/edit
Headers:
Authorization : Bearer <access_token>
Content-Type : application/json
Request:
{
"username" : "johndoe_updated" ,
"name" : "John Doe Jr." ,
"upload_id" : 456
}
Parameters:
username: Optional, 3-50 characters, must be unique
name: Optional, 3-50 characters
upload_id: Optional, reference to an existing uploaded file
Response: 200 OK
{
"code" : 200 ,
"message" : "Profile updated successfully" ,
"data" : {
"id" : 123 ,
"username" : "johndoe_updated" ,
"name" : "John Doe Jr." ,
"kyc_status" : "approved" ,
"is_active" : true ,
"is_frozen" : false ,
"upload_id" : 456
}
}
Update Profile with Image Upload
Update user profile with multipart form data including file upload.
Endpoint: PUT /api/profile
Headers:
Authorization : Bearer <access_token>
Content-Type : multipart/form-data
Request (Form Data):
file: Optional, image file (JPEG, PNG, etc.)
username: Optional, string
name: Optional, string
Response: 200 OK
{
"code" : 200 ,
"message" : "Profile updated successfully" ,
"data" : {
"id" : 123 ,
"username" : "johndoe_updated" ,
"name" : "John Doe Jr." ,
"kyc_status" : "approved" ,
"is_active" : true ,
"is_frozen" : false ,
"meta_data" : "{ \" theme \" : \" dark \" }" ,
"phone" : {
"phone" : "1234567890" ,
"country_code" : "1" ,
"verified" : true
},
"email" : {
"email" : "john@example.com" ,
"verified" : true
},
"phones" : [],
"emails" : [],
"profile_image_url" : "https://storage.rcoinx.com/uploads/new-profile.jpg?expires=..."
}
}
Example:
JavaScript
TypeScript
Flutter
async function updateProfileWithImage ( username , name , imageFile ) {
const accessToken = localStorage . getItem ( 'access_token' );
const formData = new FormData ();
if ( username ) formData . append ( 'username' , username );
if ( name ) formData . append ( 'name' , name );
if ( imageFile ) formData . append ( 'file' , imageFile );
const response = await fetch ( 'https://api.rcoinx.com/api/profile' , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ accessToken } `
// Don't set Content-Type for FormData, browser will set it with boundary
},
body: formData
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data . data ;
}
// Usage with file input
const fileInput = document . getElementById ( 'profileImage' );
const file = fileInput . files [ 0 ];
try {
const updatedProfile = await updateProfileWithImage (
'newusername' ,
'New Name' ,
file
);
console . log ( 'Profile updated:' , updatedProfile );
} catch ( error ) {
console . error ( 'Failed to update profile:' , error );
}
If the upload service (MinIO) is unavailable, you’ll receive a 503 Service Unavailable error when attempting to upload files.
Delete Profile Image
Remove the user’s profile image.
Endpoint: DELETE /api/profile/image
Headers:
Authorization : Bearer <access_token>
Response: 200 OK
{
"code" : 200 ,
"message" : "Profile image deleted successfully"
}
Example:
async function deleteProfileImage () {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/profile/image' , {
method: 'DELETE' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
}
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data ;
}
Change Email
Update the user’s email address.
Endpoint: PUT /api/email
Headers:
Authorization : Bearer <access_token>
Request:
{
"new_email" : "newemail@example.com"
}
Validation:
new_email: Required, valid email format
Email must not already exist in the system
Email must be different from current email
Response: 200 OK
{
"code" : 200 ,
"message" : "Email changed successfully"
}
Example:
async function changeEmail ( newEmail ) {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/email' , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ new_email: newEmail })
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data ;
}
Change Phone Number
Update the user’s phone number.
Endpoint: PUT /api/phone
Headers:
Authorization : Bearer <access_token>
Request:
{
"new_phone" : "9876543210" ,
"country_code" : "1"
}
Validation:
new_phone: Required, minimum 10 digits, numeric only
country_code: Required, 1-3 digits
Phone number must not already exist
Phone must be different from current number
Response: 200 OK
{
"code" : 200 ,
"message" : "Phone changed successfully"
}
Example:
async function changePhone ( newPhone , countryCode ) {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/phone' , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
new_phone: newPhone ,
country_code: countryCode
})
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data ;
}
Freeze Account
Temporarily freeze the user’s account. A frozen account cannot perform most actions.
Endpoint: POST /api/account/freeze
Headers:
Authorization : Bearer <access_token>
Request:
{
"password" : "CurrentPassword123!"
}
Validation:
password: Required, current password for verification
Account must not already be frozen
Response: 200 OK
{
"code" : 200 ,
"message" : "Account frozen successfully"
}
Side Effects:
All active sessions are revoked
User must log in again after unfreezing
Example:
async function freezeAccount ( password ) {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/account/freeze' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ password })
});
const data = await response . json ();
if ( response . ok ) {
// Clear tokens and redirect to login
localStorage . clear ();
window . location . href = '/login?frozen=true' ;
} else {
throw new Error ( data . message );
}
return data ;
}
Unfreeze Account
Unfreeze a previously frozen account.
Endpoint: POST /api/account/unfreeze
Headers:
Authorization : Bearer <access_token>
Request:
{
"password" : "CurrentPassword123!"
}
Validation:
password: Required, current password for verification
Account must be frozen
Response: 200 OK
{
"code" : 200 ,
"message" : "Account unfrozen successfully"
}
Example:
async function unfreezeAccount ( password ) {
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/account/unfreeze' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ password })
});
const data = await response . json ();
if ( ! response . ok ) {
throw new Error ( data . message );
}
return data ;
}
Delete Account
Permanently delete the user’s account (soft delete).
Endpoint: DELETE /api/account
Headers:
Authorization : Bearer <access_token>
Request:
{
"password" : "CurrentPassword123!"
}
Validation:
password: Required, current password for verification
Response: 200 OK
{
"code" : 200 ,
"message" : "Account deleted successfully"
}
Side Effects:
Account is soft-deleted (marked as inactive)
All active sessions are revoked
User data is retained but inaccessible
Example:
async function deleteAccount ( password ) {
const confirmed = confirm (
'Are you sure you want to delete your account? This action cannot be undone.'
);
if ( ! confirmed ) return ;
const accessToken = localStorage . getItem ( 'access_token' );
const response = await fetch ( 'https://api.rcoinx.com/api/account' , {
method: 'DELETE' ,
headers: {
'Authorization' : `Bearer ${ accessToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ password })
});
const data = await response . json ();
if ( response . ok ) {
// Clear all local data
localStorage . clear ();
sessionStorage . clear ();
// Redirect to goodbye page
window . location . href = '/goodbye' ;
} else {
throw new Error ( data . message );
}
return data ;
}
Account deletion is irreversible. Make sure to implement a confirmation dialog and clearly communicate the consequences to users.
Complete Profile Management Component
Here’s a comprehensive example of a profile management component:
import React , { useState , useEffect } from 'react' ;
function ProfileManager () {
const [ profile , setProfile ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
const [ editing , setEditing ] = useState ( false );
const [ formData , setFormData ] = useState ({
username: '' ,
name: '' ,
imageFile: null
});
useEffect (() => {
fetchProfile ();
}, []);
const fetchProfile = async () => {
try {
const data = await getProfile ();
setProfile ( data );
setFormData ({
username: data . username ,
name: data . name || '' ,
imageFile: null
});
} catch ( error ) {
console . error ( 'Failed to fetch profile:' , error );
} finally {
setLoading ( false );
}
};
const handleUpdate = async ( e ) => {
e . preventDefault ();
setLoading ( true );
try {
const updated = await updateProfileWithImage (
formData . username ,
formData . name ,
formData . imageFile
);
setProfile ( updated );
setEditing ( false );
alert ( 'Profile updated successfully!' );
} catch ( error ) {
alert ( 'Failed to update profile: ' + error . message );
} finally {
setLoading ( false );
}
};
const handleImageChange = ( e ) => {
const file = e . target . files [ 0 ];
if ( file ) {
if ( file . size > 5 * 1024 * 1024 ) {
alert ( 'File size must be less than 5MB' );
return ;
}
setFormData ({ ... formData , imageFile: file });
}
};
const handleDeleteImage = async () => {
if ( ! confirm ( 'Delete profile image?' )) return ;
try {
await deleteProfileImage ();
await fetchProfile ();
alert ( 'Profile image deleted' );
} catch ( error ) {
alert ( 'Failed to delete image: ' + error . message );
}
};
if ( loading && ! profile ) {
return < div > Loading... </ div > ;
}
return (
< div className = "profile-manager" >
< h2 > Profile Management </ h2 >
{ ! editing ? (
< div className = "profile-view" >
{ profile . profile_image_url && (
< div className = "profile-image" >
< img src = { profile . profile_image_url } alt = "Profile" />
< button onClick = { handleDeleteImage } > Delete Image </ button >
</ div >
) }
< div className = "profile-info" >
< p >< strong > Username: </ strong > { profile . username } </ p >
< p >< strong > Name: </ strong > { profile . name || 'Not set' } </ p >
< p >< strong > Email: </ strong > { profile . email . email } </ p >
< p >< strong > Phone: </ strong > + { profile . phone . country_code } { profile . phone . phone } </ p >
< p >< strong > KYC Status: </ strong > { profile . kyc_status } </ p >
</ div >
< button onClick = { () => setEditing ( true ) } > Edit Profile </ button >
</ div >
) : (
< form onSubmit = { handleUpdate } className = "profile-edit" >
< div >
< label > Username: </ label >
< input
type = "text"
value = { formData . username }
onChange = { ( e ) => setFormData ({ ... formData , username: e . target . value }) }
minLength = { 3 }
maxLength = { 50 }
/>
</ div >
< div >
< label > Name: </ label >
< input
type = "text"
value = { formData . name }
onChange = { ( e ) => setFormData ({ ... formData , name: e . target . value }) }
maxLength = { 50 }
/>
</ div >
< div >
< label > Profile Image: </ label >
< input
type = "file"
accept = "image/*"
onChange = { handleImageChange }
/>
</ div >
< div className = "buttons" >
< button type = "submit" disabled = { loading } >
{ loading ? 'Saving...' : 'Save Changes' }
</ button >
< button type = "button" onClick = { () => setEditing ( false ) } >
Cancel
</ button >
</ div >
</ form >
) }
</ div >
);
}
export default ProfileManager ;
Error Responses
Profile Not Found
Status: 404 Not Found
{
"code" : 404 ,
"message" : "Profile not found" ,
"error" : "User profile does not exist"
}
Username Already Exists
Status: 409 Conflict
{
"code" : 409 ,
"message" : "Username already exists" ,
"error" : "This username is already taken"
}
Email Already Exists
Status: 409 Conflict
{
"code" : 409 ,
"message" : "Email already exists" ,
"error" : "This email is already registered"
}
Incorrect Password
Status: 401 Unauthorized
{
"code" : 401 ,
"message" : "Incorrect password" ,
"error" : "The password you entered is incorrect"
}
Best Practices
1. Cache Profile Data
const profileCache = {
data: null ,
timestamp: null ,
TTL: 5 * 60 * 1000 , // 5 minutes
get : async function () {
const now = Date . now ();
if ( this . data && this . timestamp && ( now - this . timestamp < this . TTL )) {
return this . data ;
}
this . data = await getProfile ();
this . timestamp = now ;
return this . data ;
},
invalidate : function () {
this . data = null ;
this . timestamp = null ;
}
};
2. Validate Before Submission
function validateProfileUpdate ( username , name ) {
const errors = [];
if ( username && ( username . length < 3 || username . length > 50 )) {
errors . push ( 'Username must be between 3 and 50 characters' );
}
if ( name && ( name . length < 3 || name . length > 50 )) {
errors . push ( 'Name must be between 3 and 50 characters' );
}
return errors ;
}
3. Handle Image Upload Progress
async function uploadProfileWithProgress ( formData , onProgress ) {
return new Promise (( resolve , reject ) => {
const xhr = new XMLHttpRequest ();
xhr . upload . addEventListener ( 'progress' , ( e ) => {
if ( e . lengthComputable ) {
const percentComplete = ( e . loaded / e . total ) * 100 ;
onProgress ( percentComplete );
}
});
xhr . addEventListener ( 'load' , () => {
if ( xhr . status === 200 ) {
resolve ( JSON . parse ( xhr . responseText ));
} else {
reject ( new Error ( 'Upload failed' ));
}
});
xhr . addEventListener ( 'error' , () => reject ( new Error ( 'Network error' )));
xhr . open ( 'PUT' , 'https://api.rcoinx.com/api/profile' );
xhr . setRequestHeader ( 'Authorization' , `Bearer ${ localStorage . getItem ( 'access_token' ) } ` );
xhr . send ( formData );
});
}