NgamiaNgamiaDocs

Account

Register, login, and sessions

Create an account, verify an identifier, manage JWT sessions, reset passwords, and complete social login flows.

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

StepEndpointResult
1. RegisterPOST /v1/auth/registerCreates a pending account and sends a verification code.
2. VerifyPOST /v1/auth/otp/verifyActivates the account and returns a JWT access/refresh pair.
3. Call account endpointsAuthorization: Bearer <access_token>Reads profile, workspaces, and key management resources.
4. RefreshPOST /v1/auth/refreshRotates the pair when the access token expires.
5. LogoutPOST /v1/auth/logoutInvalidates the refresh token.

Register

POST/v1/auth/register
{
  "full_name": "Ada Lovelace",
  "email": "ada@example.com",
  "phone_number": "+255712345678",
  "password": "Sup3rSecret1",
  "confirm_password": "Sup3rSecret1"
}
FieldRules
full_nameRequired; at least two characters.
emailOptional, but required when phone_number is absent.
phone_numberOptional E.164 value, but required when email is absent.
passwordMust meet the server password policy; eight characters, one uppercase letter, and one digit by default.
confirm_passwordMust 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

POST/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

POST/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

POST/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

POST/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

POST/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:

POST/v1/auth/password/forgot
{ "identifier": "ada@example.com" }

Then submit the code and new password:

POST/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().

GET/v1/auth/google/start
GET/v1/auth/github/start

After 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=Bearer

Your callback route should:

  1. Read location.hash, never location.search.
  2. Parse it with new URLSearchParams(location.hash.slice(1)).
  3. Store both tokens securely on success.
  4. Immediately scrub the fragment with history.replaceState.
  5. Show the returned message if the fragment contains an error.

CamelAccounts sign-in

POST/v1/auth/camelVerified CamelAccounts access token

Send 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.