Use this file to discover all available pages before exploring further.
The Kantuta POS backend is a NestJS REST API that powers every operation in the React 19 + TypeScript frontend — from authentication and inventory management to sales, cash registers, and agent transactions. All communication happens over HTTP/HTTPS using JSON payloads, and most endpoints require a short-lived JWT access token delivered as a Bearer header.
The frontend resolves the API origin from the VITE_API_BASE_URL environment variable. If the variable is absent (for example, during local development), it falls back to http://localhost:3000.
Most endpoints are protected and require a valid JWT access token. Endpoints that do not require a token are marked [PUBLIC] throughout this reference.
Access tokens are short-lived. When any protected endpoint returns HTTP 401, the client should transparently exchange the stored refresh_token for a new access_token and replay the original request.
The frontend uses a shared Axios instance with two interceptors — one that injects the current access_token on every outgoing request, and one that catches 401 responses and attempts a token refresh before retrying.
import axios from "axios";// 1. Create the Axios instanceconst api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || "http://localhost:3000", headers: { "Content-Type": "application/json", },});// 2. Request interceptor — inject the access tokenapi.interceptors.request.use( (config) => { const token = localStorage.getItem("access_token"); if (token && config.headers) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error),);// 3. Response interceptor — refresh token on 401api.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = localStorage.getItem("refresh_token"); // Use a plain axios call to avoid triggering this interceptor again const res = await axios.post( `${import.meta.env.VITE_API_BASE_URL || "http://localhost:3000"}/auth/refresh_token`, {}, { headers: { Authorization: `Bearer ${refreshToken}` }, }, ); const newAccessToken = res.data.access_token; localStorage.setItem("access_token", newAccessToken); // Replay the original request with the new token originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return api(originalRequest); } catch (refreshError) { // Refresh token expired — force sign-out localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); window.location.href = "/signin"; return Promise.reject(refreshError); } } return Promise.reject(error); },);export default api;
Import api from this module instead of importing axios directly. Every
call made through api automatically includes the authorization header and
benefits from transparent token renewal.