Account endpoints reference · Base URL https://2fast.ng/api/
All /account/* endpoints require a date token in the Authorization header, using today's UTC date in YYYYMMDD format:
Authorization: Token 20260824
Missing or mismatched token returns HTTP 401. All requests are POST with Content-Type: application/json.
Creates a new user account.
{
"fname": "Usman",
"lname": "Halal",
"email": "usman@example.com",
"phone": "08012345678",
"password": "mypassword1",
"transpin": "5532",
"state": "Lagos",
"account": "subscriber"
}
email normalized to lowercase; valid format required.phone normalized to 11-digit Nigerian format (accepts 080…, 234…, or 10-digit).password at least 6 characters.transpin exactly 4 digits; weak PINs (0000, 1234, …) rejected.{ "status": "success", "msg": "Registration Successfull" }
| msg | Reason |
|---|---|
Phone Number Already Exist | Phone already registered |
Email Already Exist | Email already registered |
Phone Number Must Be 11 Digits | Invalid phone format |
Transaction PIN Must Be 4 Digits | PIN length wrong |
Please Set A More Secured Transaction PIN | Weak PIN |
Invalid Email Format | Email malformed |
Password Must Be At Least 6 Characters | Password too short |
Authenticates by phone + password.
{ "phone": "08012345678", "accesspass": "mypassword1" }
{
"status": "success",
"msg": "Login Successful",
"name": "Usman Halal",
"phone": "08012345678"
}
Failed attempts are tracked per phone and per IP. After 5 fails in 5 minutes, the account/IP is locked for 5 minutes. Every attempt is logged with browser, OS, IP, and user-agent.
| msg | Reason |
|---|---|
Incorrect credentials. Attempt 1 of 5 | Wrong phone/password |
Incorrect credentials. Attempt 4 of 5 ⚠️ | 1–2 attempts left |
Too many failed attempts. Please try again after 5 minutes at 14:30:00 | Locked for 5 minutes |
Account Blocked, Please Contact Customer Support | account_status not active |
Sends a 4-digit recovery code to the user's email via Resend (from support@2fast.ng).
{ "email": "usman@example.com" }
{ "status": "success", "msg": "A 4-digit recovery code has been sent to your email" }
Stored in otps (otp_type=recovery, pending, expires in 10 minutes).
| msg | Reason |
|---|---|
Invalid Email Format | Email malformed |
Email Not Found | No user with that email |
Failed to send recovery email | Resend delivery failed |
Verifies a recovery code without changing the password.
{ "email": "usman@example.com", "code": "5678" }
{ "status": "success", "msg": "Code Verified Successfully" }
| msg | Reason |
|---|---|
Invalid Email Format | Email malformed |
Invalid Code | Code wrong / used / not found |
Code Expired | OTP older than 10 minutes |
Email Not Found | No user with that email |
Sets a new password using a valid recovery code (pending or already-verified).
{
"email": "usman@example.com",
"code": "5678",
"password": "mynewpassword1"
}
{ "status": "success", "msg": "Password Updated Successfully" }
The OTP row is marked used after the password update.
| msg | Reason |
|---|---|
Invalid Email Format | Email malformed |
Invalid Code | Code wrong / used / not found |
Code Expired | OTP older than 10 minutes |
Password Must Be At Least 6 Characters | Password too short |
Email Not Found | No user with that email |
All /user/* endpoints authenticate with the user's API key instead of the date token. Send it in the Authorization header:
Authorization: Bearer BAHCCvJ4y36...
The API key is returned by /account/login and shown on /user as api_key. Missing or unknown keys return { "status": "fail", "msg": "User not found" }.
Generates a 4-digit email-verification code, sends it via Resend, and invalidates any previous pending code.
{}
{ "status": "success", "msg": "Verification code sent to u***@gmail.com" }
| msg | Reason |
|---|---|
Your email is already verified | No need to resend |
Failed to send email. Please try again | Mail server error |
User not found | Invalid API key |
Verifies the 4-digit code and marks the user's email as verified.
{ "code": "4821" }
{
"status": "success",
"msg": "Email verified successfully",
"email": "user@example.com"
}
| msg | Reason |
|---|---|
Verification code is required | Missing code field |
Invalid verification code | Code is not numeric |
Incorrect verification code. Please check your email and try again | Code doesn't match / expired |
Your email is already verified | Already verified |
Returns the authenticated user's profile.
{}
{
"status": "success",
"name": "Usman Halal",
"fname": "Usman", "lname": "Halal",
"email": "user@example.com",
"phone": "08012345678",
"state": "Lagos",
"balance": "5000.00",
"referral_wallet": "200.00",
"cashback_wallet": "50.00",
"referral_code": "08012345678",
"account_tier": "tier1",
"user_type": "subscriber",
"kyc_verified": false,
"api_key": "BAHCCvJ4y36..."
}
Paginated list of the user's transactions, newest first.
{ "limit": 20, "offset": 0 }
{
"status": "success",
"transactions": [
{
"tId": 101, "transref": "DATA20260303001",
"servicename": "Data Bundle",
"servicedesc": "1GB MTN data for 08012345678",
"amount": 300.00, "status": 0,
"oldbal": 5000.00, "newbal": 4700.00,
"date": "2026-03-03 20:00:00"
}
]
}
Details for a single transaction by reference.
{ "ref": "DATA20260303001" }
{
"status": "success",
"transaction": {
"tId": 101, "transref": "DATA20260303001",
"servicename": "Data Bundle",
"servicedesc": "1GB MTN data for 08012345678",
"amount": 300.00, "status": 0, "status_label": "Successful",
"oldbal": 5000.00, "newbal": 4700.00,
"date": "2026-03-03 20:00:00"
}
}
Returns Transaction not found if the ref doesn't exist or belongs to another user — both cases look identical for security.
Double-duty endpoint. Empty JSON body returns the current picture. Multipart body uploads a new one and deletes the previous file.
Content-Type: application/json
{}
{
"status": "success",
"has_picture": true,
"image_path": "/user-uuid/profile_1234567890.jpg",
"image_url": "https://.../signed-url"
}
When there's no picture, has_picture is false and both URL fields are null.
Content-Type: multipart/form-data Field name: profile_picture Allowed: JPG, JPEG, PNG Max size: 5MB
{
"status": "success",
"msg": "Profile picture updated successfully",
"image_path": "/user-uuid/profile_1234567890.jpg",
"image_url": "https://.../signed-url"
}
| msg | Reason |
|---|---|
Invalid file type. Only JPG and PNG allowed. | Wrong file extension |
File too large. Maximum size is 5MB. | File exceeds 5MB |
Uploaded file is not a valid image. | File is empty or corrupt |
Failed to save image. Check folder permissions. | Server write error |
Removes the current profile picture. Safe to call when none exists.
{}
{ "status": "success", "msg": "Profile picture removed successfully" }
curl -X POST https://2fast.ng/account/login \\
-H "Content-Type: application/json" \\
-H "Authorization: Token 20260824" \\
-d '{"phone":"08012345678","accesspass":"mypassword1"}'
Replace 20260824 with today's UTC date in YYYYMMDD.
Returns all funding (virtual) accounts linked to the user.
{}
{
"status": "success",
"accounts": [
{
"provider": "safehaven",
"account_number": "1234567890",
"account_name": "Usman Halal",
"bank_name": "Safehaven Bank"
}
]
}
Empty accounts array means the user hasn't generated a virtual account yet — call /user/generate-account.
| msg | Reason |
|---|---|
Unable to fetch accounts | Database read error |
User not found | Invalid or missing API key |
Generates a Safehaven virtual account. Idempotent — returns the existing one if already generated.
{}
{
"status": "success",
"msg": "Safehaven virtual account generated successfully",
"account": "1234567890"
}
{
"status": "success",
"msg": "Account already generated",
"account": "1234567890"
}
| msg | Reason |
|---|---|
Unable to generate account at this time. Safehaven may be unavailable. Please try again later. | Missing SAFEHAVEN_TOKEN, Safehaven API is down, upstream error, or DB insert failed |
User not found | Invalid or missing API key |
Shows how much of today's spending limit remains. Advises whether to upgrade or wait for the reset.
{}
{
"status": "success",
"limit_hit": false,
"action": "ok",
"message": "You have ₦38,000.00 remaining of your daily limit.",
"daily_limit": 50000,
"spent_today": 12000,
"remaining": 38000,
"percent_used": 24,
"current_tier": 1,
"can_upgrade": true,
"reset_in_seconds": 32400,
"reset_in": "9h 0m",
"resets_at": "2026-08-26T00:00:00.000Z"
}
{
"status": "success",
"limit_hit": true,
"action": "upgrade",
"message": "You've reached your daily limit. Upgrade your account to increase it.",
"daily_limit": 50000,
"spent_today": 50000,
"remaining": 0,
"percent_used": 100,
"current_tier": 1,
"can_upgrade": true,
"reset_in_seconds": 32400,
"reset_in": "9h 0m"
}
action is ok (below limit), upgrade (limit hit, upgrade possible), or wait (limit hit, already max tier).
| msg | Reason |
|---|---|
User not found | Invalid or missing API key |
Returns the user's current tier plus all available tier levels.
{}
{
"status": "success",
"current_tier": 1,
"daily_limit": 50000,
"max_balance": 300000,
"tier_levels": [
{ "tier": 1, "name": "Basic", "daily_limit": 50000, "requires_kyc": false },
{ "tier": 2, "name": "Standard", "daily_limit": 200000, "requires_kyc": true },
{ "tier": 3, "name": "Premium", "daily_limit": 1000000, "requires_kyc": true }
]
}
| msg | Reason |
|---|---|
User not found | Invalid or missing API key |
Lists requirements for all tiers (or one specific tier). Authentication is optional — when the API key is sent, each requirement is marked as met/unmet.
{ "tier": 2 } // optional; omit to return all tiers
{
"status": "success",
"user_current_tier": 1,
"tiers": [
{
"tier_level": 2,
"tier_name": "Standard",
"color": "#3B82F6",
"daily_limit_formatted": "₦200,000.00",
"max_balance_formatted": "₦500,000.00",
"is_unlimited_balance": false,
"requirements_count": 3,
"requirements": [
{ "key": "email_verified", "label": "Email Verified", "description": "Verify your email address", "required": true, "met": true },
{ "key": "phone_verified", "label": "Phone Verified", "description": "Verify your phone number", "required": true, "met": false },
{ "key": "bvn", "label": "BVN Verified", "description": "Verify your BVN", "required": true, "met": false }
],
"all_requirements_met": false,
"is_current_tier": false,
"can_upgrade_to": true
}
]
}
When called without an API key, the met, all_requirements_met, is_current_tier, can_upgrade_to, and user_current_tier fields are omitted.
Requests an upgrade. Tier 2 auto-approves if requirements are met; Tier 3 is queued for admin review.
{ "tier": 2 }
{
"status": "success",
"msg": "Congratulations! Your account has been upgraded to Tier 2.",
"auto_approved": true
}
{
"status": "success",
"msg": "Your Tier 3 upgrade request has been submitted for admin review.",
"auto_approved": false
}
| msg | Reason |
|---|---|
Invalid target tier. Use 2 or 3. | Body tier is not 2 or 3 |
Your account is already at Tier X or higher | Downgrade or same tier |
You already have a pending upgrade request for Tier X | Duplicate pending request |
Requirements not met. Missing: Phone Verified, BVN Verified | One or more required KYC items missing |
Upload proof of address as multipart/form-data, field name proof_of_address. Required before Tier 3 upgrade. JPG/PNG/PDF, max 5MB. Any previously uploaded file is replaced.
{
"status": "success",
"msg": "Proof of address uploaded successfully. It will be reviewed when you request a Tier 3 upgrade.",
"file": "address_proof_1712345678901.pdf",
"file_url": "https://.../signed-url",
"proof_of_address_status": 0
}
proof_of_address_status: 0 pending, 1 approved, 2 rejected.
| msg | Reason |
|---|---|
Proof of address is only required for Tier 3 upgrade. Please upgrade to Tier 2 first. | User is still Tier 1 |
Your account is already at Tier 3. No further proof of address is needed. | Already max tier |
No file uploaded. Please attach your proof of address document. | Missing multipart or field |
Invalid file type. Only JPG, PNG, and PDF are accepted. | Wrong extension |
File too large. Maximum size is 5MB. | Exceeds 5MB |
Uploaded file appears to be corrupt or invalid. | Empty file |
Failed to save file. Please check server folder permissions. | Storage upload error |
Verifies BVN against the user's registered name and date of birth via Monnify.
{ "bvn": "12345678901", "dob": "1990-05-14" }
{
"status": "success",
"msg": "BVN Verified Successfully",
"name_match": 100,
"dob_match": 100
}
| msg | Reason |
|---|---|
BVN Must Be 11 Digits | Wrong length / non-numeric |
Invalid date format. Use yyyy-mm-dd or dd-Mon-yyyy | Unrecognised DOB |
BVN already verified on this account | Already done previously |
BVN verification is unavailable. Please try again later. | Missing MONNIFY_TOKEN |
BVN verification service unreachable | Monnify network error |
BVN verification failed | Monnify rejected the request |
BVN details do not match your registered name | Name match < 50% |
Verifies NIN against the user's registered first/last name.
{ "nin": "12345678901" }
{
"status": "success",
"msg": "NIN Verified Successfully",
"name_match": 92
}
| msg | Reason |
|---|---|
NIN Must Be Numeric | Non-numeric characters |
NIN Must Be 11 Digits | Wrong length |
NIN already verified on this account | Already done previously |
NIN verification is unavailable. Please try again later. | Missing MONNIFY_TOKEN |
NIN verification service unreachable | Monnify network error |
NIN verification failed | Monnify rejected the request |
NIN details do not match your registered name | Name similarity < 50% |
Returns referral code, count, wallet balance and total earned.
{}
{
"status": "success",
"referral_code": "08012345678",
"referral_count": 4,
"referral_wallet": "1200.00",
"total_earned": "1500.00"
}
| msg | Reason |
|---|---|
User not found | Invalid or missing API key |
Moves funds from the referral wallet into the main wallet. Logs a REFTW… transaction.
{ "amount": 500, "transpin": "5532" }
{
"status": "success",
"msg": "Transfer Successful",
"main_wallet": "5500.00",
"referral_wallet": "700.00"
}
| msg | Reason |
|---|---|
Invalid Amount | Missing or non-positive amount |
Invalid Transaction PIN | PIN doesn't match |
Insufficient Referral Balance | Referral wallet < amount |
Transfer failed | Database update error |
Moves funds from the cashback wallet into the main wallet. Logs a CBTW… transaction.
{ "amount": 500, "transpin": "5532" }
{
"status": "success",
"msg": "Transfer Successful",
"main_wallet": "5500.00",
"cashback_wallet": "50.00"
}
| msg | Reason |
|---|---|
Invalid Amount | Missing or non-positive amount |
Invalid Transaction PIN | PIN doesn't match |
Insufficient Cashback Balance | Cashback wallet < amount |
Transfer failed | Database update error |
Transfers from the main wallet to another 2Fast user identified by phone number. Both sides get a transaction record.
{ "phone": "08099887766", "amount": 1000, "transpin": "5532" }
{
"status": "success",
"msg": "Transfer Successful",
"new_balance": 4000
}
| msg | Reason |
|---|---|
Invalid phone number | Phone couldn't be normalised |
Invalid Amount | Missing or non-positive amount |
Invalid Transaction PIN | PIN doesn't match |
You cannot transfer to yourself | Recipient phone equals sender |
Insufficient Balance | Main balance < amount |
Recipient not found | No 2Fast user with that phone |
Transfer failed | Database update error (rolled back) |
Lists all saved beneficiaries for the user, newest first.
{}
{
"status": "success",
"beneficiaries": [
{ "id": 12, "phone": "08099887766", "name": "Ahmed", "network": "MTN" }
]
}
network is null when the phone prefix doesn't match a known Nigerian network.
Saves a beneficiary. Duplicate phone updates the existing record (upsert on user_id + phone). Network is auto-detected from the phone prefix.
{ "phone": "08099887766", "name": "Ahmed" }
{ "status": "success", "msg": "Beneficiary Added" }
| msg | Reason |
|---|---|
Invalid phone number | Phone couldn't be normalised |
Name is required | Missing/empty name |
Failed to add beneficiary | Database write error |
Removes a saved beneficiary by its numeric id. Users can only delete their own beneficiaries.
{ "id": 12 }
{ "status": "success", "msg": "Beneficiary Removed" }
| msg | Reason |
|---|---|
Invalid beneficiary id | Missing or non-numeric id |
Beneficiary not found | ID doesn't exist or belongs to another user |
Failed to remove beneficiary | Database delete error |