Authentication
The SabiBooks API uses JWT (JSON Web Token) authentication. Unlike traditional username/password systems, SabiBooks authenticates via phone number + OTP (one-time password sent by SMS). This reflects how Nigerian merchants typically operate — by phone number, not email.
Authentication Flow
Section titled “Authentication Flow”- Register your phone number to create an account.
- Verify the OTP sent via SMS to confirm your number.
- Login with your verified phone number to request a new OTP.
- Verify login OTP to receive access and refresh tokens.
- Use the access token in the
Authorizationheader for all API requests. - Refresh the token before it expires using the refresh token.
Step 1: Register
Section titled “Step 1: Register”Create a new account with a Nigerian phone number.
curl -X POST https://app.sabibooks.com/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "phone": "+2348012345678", "first_name": "Chidi", "last_name": "Okonkwo", "business_name": "Chidi Provisions" }'Response:
{ "success": true, "message": "Registration successful. OTP sent to +2348012345678", "data": { "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "phone": "+2348012345678", "otp_expires_at": "2026-02-20T14:32:08Z" }, "timestamp": "2026-02-20T14:22:08Z"}Step 2: Verify Registration OTP
Section titled “Step 2: Verify Registration OTP”Verify the SMS code sent to your phone to activate the account.
curl -X POST https://app.sabibooks.com/api/v1/auth/verify-otp \ -H "Content-Type: application/json" \ -d '{ "phone": "+2348012345678", "otp": "123456" }'Response:
{ "success": true, "message": "Phone number verified successfully", "data": { "verified": true, "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 3600 }, "timestamp": "2026-02-20T14:23:15Z"}Step 3: Login (Returning Users)
Section titled “Step 3: Login (Returning Users)”For returning users, request a login OTP:
curl -X POST https://app.sabibooks.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "phone": "+2348012345678" }'Response:
{ "success": true, "message": "OTP sent to +2348012345678", "data": { "phone": "+2348012345678", "otp_expires_at": "2026-02-20T15:02:08Z" }, "timestamp": "2026-02-20T14:52:08Z"}Step 4: Verify Login OTP
Section titled “Step 4: Verify Login OTP”Submit the OTP to receive your tokens:
curl -X POST https://app.sabibooks.com/api/v1/auth/verify-otp \ -H "Content-Type: application/json" \ -d '{ "phone": "+2348012345678", "otp": "654321" }'Response:
{ "success": true, "message": "Login successful", "data": { "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 3600 }, "timestamp": "2026-02-20T14:53:22Z"}Step 5: Using the Access Token
Section titled “Step 5: Using the Access Token”Include the access token in the Authorization header of every API request:
curl -X GET https://app.sabibooks.com/api/v1/products \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \ -H "Content-Type: application/json"The token contains encoded claims including your user_id, business_id, and roles. The API uses these claims to scope all data to your business automatically.
Step 6: Refreshing the Token
Section titled “Step 6: Refreshing the Token”Access tokens expire after 1 hour. Use the refresh token to obtain a new access token without re-authenticating:
curl -X POST https://app.sabibooks.com/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "eyJhbGciOiJIUzI1NiJ9..." }'Response:
{ "success": true, "message": "Token refreshed successfully", "data": { "access_token": "eyJhbGciOiJIUzI1NiJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 3600 }, "timestamp": "2026-02-20T15:22:08Z"}Token Lifetimes
Section titled “Token Lifetimes”| Token | Lifetime | Purpose |
|---|---|---|
| Access Token | 1 hour | Authenticates API requests |
| Refresh Token | 30 days | Obtains new access tokens |
| OTP Code | 10 minutes | Verifies phone ownership |
JWT Claims
Section titled “JWT Claims”The access token payload includes:
{ "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "business_id": "b1c2d3e4-f5a6-7890-bcde-f12345678901", "roles": ["BUSINESS_OWNER"], "iat": 1740057728, "exp": 1740061328}| Claim | Description |
|---|---|
sub | User ID (UUID) |
business_id | The business this user belongs to — all API responses are scoped to this business |
roles | User roles (e.g., BUSINESS_OWNER, STAFF) |
iat | Token issue time (Unix timestamp) |
exp | Token expiry time (Unix timestamp) |
Public Endpoints
Section titled “Public Endpoints”These endpoints do not require authentication:
| Endpoint | Method | Purpose |
|---|---|---|
/auth/register | POST | Create new account |
/auth/login | POST | Request login OTP |
/auth/verify-otp | POST | Verify OTP and get tokens |
/auth/refresh | POST | Refresh access token |
/actuator/health | GET | Service health check |
/swagger-ui/** | GET | Interactive API docs |
/api-docs/** | GET | OpenAPI specification |
Error Responses
Section titled “Error Responses”Invalid or Expired Token (401)
Section titled “Invalid or Expired Token (401)”{ "success": false, "message": "Access token has expired", "error": { "type": "https://api.sabibooks.com/errors/authentication", "title": "Unauthorized", "status": 401, "detail": "The provided access token has expired. Use the refresh endpoint to obtain a new token." }, "timestamp": "2026-02-20T15:22:08Z"}Invalid OTP (400)
Section titled “Invalid OTP (400)”{ "success": false, "message": "Invalid OTP", "error": { "type": "https://api.sabibooks.com/errors/validation", "title": "Bad Request", "status": 400, "detail": "The OTP code provided is invalid or has expired. Request a new OTP." }, "timestamp": "2026-02-20T14:25:00Z"}