SendAfrica logoSendAfricaDocs

Auth & Accounts

Registration, email/phone OTP verification, login, JWT refresh rotation, password reset, OAuth linking, and API key management endpoints.


One accounts row backs both access modes. New accounts receive a 6-digit email OTP automatically; login is blocked until the email is verified (403 email_not_verified) unless the server sets RELAX_EMAIL_VERIFICATION_FOR_LOGIN=true.

#Endpoints

MethodPathAuthDescription
POST/v1/auth/registerPublicCreate account (sends OTP email)
POST/v1/auth/loginPublicGet JWT token pair
POST/v1/auth/refreshPublicRotate refresh → new access token
POST/v1/auth/send-verification-emailPublicTrigger email OTP
POST/v1/auth/verify-emailPublicConfirm email OTP
POST/v1/auth/reset-passwordPublicRequest password-reset OTP
POST/v1/auth/reset-password-confirmPublicConfirm reset with OTP + new password
GET/v1/auth/google, /githubPublicStart OAuth login/register (302)
GET/v1/auth/google/callbackPublicProvider redirect target
POST/v1/auth/oauth/exchangePublicRedeem one-time code for JWT pair
GET/v1/auth/meJWTCurrent user profile
PUT/v1/auth/meJWTUpdate profile / phone
POST/v1/auth/logoutJWTBlacklist token + revoke refresh
POST/v1/auth/change-passwordJWTChange password (revokes other sessions)
POST/v1/auth/send-phone-otpJWTPhone verification via SMS
POST/v1/auth/verify-phoneJWTConfirm phone OTP
POST/v1/auth/google/linkJWTLink Google identity to account
POST/v1/auth/google/contacts-connectJWTGoogle Contacts sync consent flow
GET/POST/v1/auth/api-keysJWTList / create developer API keys
DELETE/v1/auth/api-keys/{keyId}JWTRevoke an API key

#Register

POST/v1/auth/registerPublic
bash
bash
curl -s -X POST https://api.sendafrica.online/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",
    "password": "a-strong-password-12plus",
    "company_name": "Acme Ltd",
    "phone": "0712345678"
  }'
201 Created
json
{
  "success": true,
  "data": {
    "account_id": "486f8a6e-ea75-47ea-b176-c8e931aed058",
    "email": "john@example.com",
    "message": "Registration successful. Please verify your email."
  }
}
FieldTypeRequiredRules
first_namestringYesMax 100 chars
last_namestringYesMax 100 chars
emailstringYesValid email; unique
passwordstringYes12–128 characters
company_namestringNoMax 255 chars
phonestringNoValid Tanzania mobile if provided

#Verify email

POST/v1/auth/verify-emailPublic
bash
bash
curl -s -X POST https://api.sendafrica.online/v1/auth/verify-email \
  -H "Content-Type: application/json" \
  -d '{ "email": "john@example.com", "otp": "482910" }'
200 OK
json
{
  "success": true,
  "data": {
    "message": "Email successfully verified",
    "email_verified": true,
    "can_login": true
  }
}

send-verification-email always returns 200 whether or not the address exists — this prevents email enumeration. OTPs expire after 15 minutes.

#Login

POST/v1/auth/loginPublic
200 OK
json
{
  "success": true,
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "...",
    "token_type": "Bearer",
    "expires_in": 900,
    "email_verified": true,
    "phone_verified": false,
    "profile_complete": true
  }
}
  • expires_in is seconds (900 = 15 min). Refresh before expiry — see Authentication.
  • email_verified / phone_verified / profile_complete let clients render onboarding banners without an extra GET /me call.

#OAuth exchange flow

After the provider redirects to your frontend with ?exchange_code=..., redeem it server-side within its short TTL. The code is single-use:

bash
bash
curl -s -X POST https://api.sendafrica.online/v1/auth/oauth/exchange \
  -H "Content-Type: application/json" \
  -d '{"exchange_code": "from-the-redirect"}'

Response is the same JWT pair as login. Errors: 401 invalid_exchange_code when expired or already used. To link a provider to an existing logged-in account use POST /v1/auth/google/link (or /github/link) with JWT instead.

#API keys

GET/v1/auth/api-keysJWT

Listing returns metadata only — never raw keys. Creating returns the full key exactly once; deleting revokes immediately. See Authentication for the creation example and storage guidance.