Skip to main content

Customer Auth Flow

Customer authentication uses a 3-step email OTP flow. There are no passwords.

Step 1: Request OTP

POST /access/api/v1/auth

Body:

{ "email": "customer@example.com" }

The API sends a 6-digit code to the customer's email and returns a verification id:

{
"status": "success",
"data": { "id": "69e885ba4f0e4f248bc572fe" }
}

Store the id in component state — you need it in step 2.

Possible errors:

CodeWhat to show
ACCOUNT_BLACKLISTED"Your account has been suspended"
TOO_MANY_LOGIN_ATTEMPTS"Too many attempts, please wait before trying again"
AUTH_NOT_INITIALIZED"Login is not available for this store"

Step 2: Verify OTP

POST /access/api/v1/auth/verify

Body:

{
"email": "customer@example.com",
"id": "69e885ba4f0e4f248bc572fe",
"code": "217273",
"event": "login"
}

On success, returns the JWT token:

{
"status": "success",
"data": { "token": "eyJhbGci..." }
}

Possible errors:

CodeWhat to show
INVALID_CODE"Incorrect code, please try again"
VERIFICATION_NOT_VALID"Code has expired, please request a new one"
TOO_MANY_VERIFY_ATTEMPTS"Too many attempts, please request a new code"

Step 3: Store and use the token

localStorage.setItem('dn_token', data.token);

Pass the token on all protected requests:

Authorization: Bearer eyJhbGci...

Getting the logged-in customer

GET /access/api/v1/auth
Authorization: Bearer <token>

Returns the customer's full profile.

Updating the profile

PATCH /access/api/v1/auth
Authorization: Bearer <token>

Body:

{
"name": "Jane Doe",
"phone": "0712345678",
"town": "Nairobi",
"city": "",
"apartment": "Westlands",
"room": "4B",
"postalCode": ""
}

name, phone, and town are required. The rest are optional address fields.

Customers can log in from a link in their order confirmation email without entering a code. This is the "view your order" flow.

POST /access/api/v1/auth/access

Body:

{
"id": "link_id",
"email": "customer@example.com",
"order": "order_id"
}

Returns the same JWT token as the OTP flow.

Possible errors:

CodeWhat to show
INVALID_ACCESS_LINK"This link is invalid"
ACCESS_LINK_EXPIRED"This link has expired"
ORDER_CANCELLED"This order was cancelled"

Logout

A server-side logout endpoint is not yet available. For now, logout is handled client-side — delete the token from storage and clear any customer state:

localStorage.removeItem('dn_token');
router.push('/');

Handling expired tokens

When any protected request returns SESSION_EXPIRED or AUTH_TOKEN_INVALID, clear the token and redirect to login. If you want to preserve the customer's destination, pass it as a query param:

router.push(`/login?redirect=${encodeURIComponent(currentPath)}`);