An unofficial TypeScript SDK for interacting with the FPT Academic Portal (FAP).
While the main portal at fap.fpt.edu.vn is built with ASP.NET and primarily serves server-rendered HTML, the official MyFAP mobile app uses a set of REST APIs behind the scenes.
The APIs were documented by observing and reverse engineering the behavior of the MyFAP mobile app. The result is a friendly, strongly typed, runtime-validated interface for working with FAP data from Node.js and TypeScript.
Important
This project is not affiliated with, endorsed by, or supported by FPT University. The underlying APIs are undocumented and may change without notice. Use the SDK only with accounts and data you are authorized to access, and follow all applicable policies and terms.
- Node.js 22 or later
- An FPT University account with access to FAP
npm install git+https://github.com/CocStack/fap-sdk.git#main
# or
pnpm add git+https://github.com/CocStack/fap-sdk.git#main
# or
yarn add git+https://github.com/CocStack/fap-sdk.git#mainFor reproducible installations, pin a release tag or commit instead of main.
import { MyFapClient } from 'fap-sdk';
const client = new MyFapClient();
const schedule = await client.getActivityStudentByWeek(
'APHL',
'HE123456',
'YOUR_AUTHEN_KEY',
{ week: 30, semester: 'Summer2026', year: 2026 },
);
console.log(schedule);Most student endpoints require a campus code, roll number, and FAP authenKey. Checksums required by MyFAP are generated internally.
FeID authentication uses an interactive PKCE flow. Retain the verifier and state while the user signs in, validate the callback state, and exchange each authorization code only once.
import { randomUUID } from 'node:crypto';
import { FeIdAuthenticator, MyFapClient } from 'fap-sdk';
const feid = new FeIdAuthenticator();
const state = randomUUID();
const { url, codeVerifier } = feid.getAuthorizeUrl(state);
// 1. Save `state` and `codeVerifier` securely for this login attempt.
// 2. Open `url` in the user's browser.
// 3. Obtain the callback URL from the FeID redirect.
declare const callbackUrl: string;
const callback = new URL(callbackUrl);
if (callback.searchParams.get('state') !== state) {
throw new Error('Invalid OAuth state');
}
const callbackCode = callback.searchParams.get('code');
if (!callbackCode) throw new Error('Missing authorization code');
const tokens = await feid.exchangeCode(callbackCode, codeVerifier);
const client = new MyFapClient();
const student = await client.authenticateByFeId(tokens.access_token, 'APHL');
console.log(student.rollnumber, student.authenKey);How the callback is handed back depends on your application environment. Never log or commit authorization codes, access tokens, refresh tokens, passwords, or FAP authenKey values.
The authenticator also supports token lifecycle operations:
const refreshed = await feid.refreshToken(refreshToken);
if (refreshed.refresh_token) {
await feid.revokeToken(refreshed.refresh_token, 'refresh_token');
}MyFapClient accepts a single optional configuration object:
import {
FetchHttpTransport,
MyFapClient,
type HttpTransport,
} from 'fap-sdk';
const client = new MyFapClient({ timeoutMs: 20_000 });
// A custom transport can be used for testing, tracing, or another HTTP stack.
const transport: HttpTransport = new FetchHttpTransport();
const customClient = new MyFapClient({ transport, timeoutMs: 10_000 });The built-in transport defaults to a 36-second timeout. timeoutMs must be a positive finite number. userAgent configures only the built-in transport; a custom transport is responsible for its own headers.
getAllActiveCampus()authenticateByFeId()authenticateByGoogleAccessToken()authenticateByUsername()getCampusInfo()getStudentProfile()retrieveImage()getBalance()checkUpdateProfile()
getSubjects()getSemester()getSubjectBySemester()getWeekByDate()getActivityStudent()getActivityStudentByWeek()getScheduleExam()getApplication()
getStudentAttendances()getCourseAttendance()getStudentMark()getMarkByCourse()getAcademicTranscript()getDiemPhongTrao()
getNotificationByRoll()getTop10News()searchNews()updateTokenDevices()
checkOpenFeedBack()getStudentRate()addRate()
All SDK errors extend MyFapError and expose a stable code. Errors marked as retryable may be retried with an appropriate delay.
import {
MyFapAuthExpiredError,
MyFapError,
MyFapRateLimitError,
MyFapValidationError,
} from 'fap-sdk';
try {
const semesters = await client.getSemester('APHL', authenKey);
console.log(semesters);
} catch (error) {
if (error instanceof MyFapAuthExpiredError) {
// Start authentication again.
} else if (error instanceof MyFapRateLimitError) {
// Respect error.retryAfterMs before retrying.
} else if (error instanceof MyFapValidationError) {
// The upstream response no longer matches the expected schema.
} else if (error instanceof MyFapError) {
console.error(error.code, error.message, error.retryable);
} else {
throw error;
}
}Common error classes cover network, timeout, abort, HTTP, rate-limit, server, authentication, checksum, response, and validation failures.
- Treat the upstream API as unstable because it is not publicly documented.
- Avoid concurrent request bursts and respect rate-limit responses.
- Store credentials and tokens encrypted when persisted.
- Do not expose FAP data to users who are not authorized to view it.
- Validate and sanitize data before displaying it in another application.
MIT