The authentication base path is /v1/auth. Authentication routes are rate limited by client IP; the default is 20 requests per minute. Registration, login, verification, refresh, and password reset do not need a bearer token. Logout requires a JWT.
The account flow
| Step | Endpoint | Result |
|---|---|---|
| 1. Register | POST /v1/auth/register | Creates a pending account and sends a verification code. |
| 2. Verify | POST /v1/auth/otp/verify | Activates the account and returns a JWT access/refresh pair. |
| 3. Call account endpoints | Authorization: Bearer <access_token> | Reads profile, workspaces, and key management resources. |
| 4. Refresh | POST /v1/auth/refresh | Rotates the pair when the access token expires. |
| 5. Logout | POST /v1/auth/logout | Invalidates the refresh token. |
Register
/v1/auth/register{
"full_name": "Ada Lovelace",
"email": "ada@example.com",
"phone_number": "+255712345678",
"password": "Sup3rSecret1",
"confirm_password": "Sup3rSecret1"
}| Field | Rules |
|---|---|
full_name | Required; at least two characters. |
email | Optional, but required when phone_number is absent. |
phone_number | Optional E.164 value, but required when email is absent. |
password | Must meet the server password policy; eight characters, one uppercase letter, and one digit by default. |
confirm_password | Must match password. |
The response is 201 with status: "pending". At least one identifier must be verified before the account becomes active. If both email and phone are supplied, each receives its own code.
Verify an OTP
/v1/auth/otp/verify{
"identifier": "ada@example.com",
"channel": "email",
"purpose": "verify_email",
"code": "482910"
}Codes are six digits and expire after five minutes. A successful response returns:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "u373EcwZY...",
"expires_in": 900,
"token_type": "Bearer"
}Store both tokens securely. The access token lasts 15 minutes; the refresh token lasts 30 days and rotates after each successful refresh.
Resend a code
/v1/auth/otp/resend{
"identifier": "ada@example.com",
"channel": "email",
"purpose": "verify_email"
}Resends are subject to a cooldown and per-identifier limit. A 429 rate_limited response means the client should wait rather than repeatedly resend.
Login
/v1/auth/login{
"identifier": "ada@example.com",
"password": "Sup3rSecret1"
}A successful login returns an access and refresh token pair. Some account flows may require an additional verification step; follow the response’s documented next action and never expose verification codes in logs or client analytics.
For security, invalid credentials use the same response whether the identifier does not exist or the password is wrong.
Refresh a session
/v1/auth/refresh{ "refresh_token": "u373EcwZY..." }The response contains a new access token and refresh token. Overwrite both stored values; the old refresh token becomes invalid after rotation. On a 401 unauthorized, send the user through login again.
Logout
/v1/auth/logoutJWT{ "refresh_token": "u373EcwZY..." }Logout is idempotent. Discard both tokens in the client regardless of the response.
Reset a password
Start the flow:
/v1/auth/password/forgot{ "identifier": "ada@example.com" }Then submit the code and new password:
/v1/auth/password/reset{
"identifier": "ada@example.com",
"otp": "482910",
"new_password": "NewSecret1",
"confirm_password": "NewSecret1"
}The forgot-password response does not reveal whether an account exists. Show a generic confirmation in the UI.
Google and GitHub login
Social login endpoints are browser navigations, not JSON calls. Use a link, window.location, or a browser/WebView navigation; do not call them with fetch().
/v1/auth/google/start/v1/auth/github/startAfter consent, the API redirects to your frontend callback with tokens in the URL fragment:
/auth/callback#access_token=...&refresh_token=...&expires_in=900&token_type=BearerYour callback route should:
- Read
location.hash, neverlocation.search. - Parse it with
new URLSearchParams(location.hash.slice(1)). - Store both tokens securely on success.
- Immediately scrub the fragment with
history.replaceState. - Show the returned message if the fragment contains an error.
CamelAccounts sign-in
/v1/auth/camelVerified CamelAccounts access tokenSend a verified CamelAccounts access token to start or resume a Ngamia session without a password. The API validates the token, auto-creates the matching local account when needed, and returns the same JWT access/refresh pair as the other login flows:
{
"access_token": "eyJhbGciOiJkaXIiLCJraWQiOi...",
"refresh_token": "u373EcwZY...",
"expires_in": 900,
"token_type": "Bearer"
}Store the returned pair securely and rotate it through /v1/auth/refresh as usual.