Unofficial TypeScript SDK for the FitDays API.
Covers email/phone login and data sync (syncFromServer), with full typings for the response — including automatic JSON parsing of the ext_data field on weight measurements.
import { FitDaysClient } from 'fitdays-api'
const client = new FitDaysClient({ region: 'us' })
await client.login('foo@example.com', 'my-password')
const sync = await client.syncAll()
console.log(sync.data.weight_list[0]?.weight_kg)- Install & requirements
- Quick start
- API —
FitDaysClient - Sync response types
- Error handling
- Recipes
- Project layout
- Scripts
- Disclaimer
- Node.js ≥ 22 (the SDK is pure ESM with
NodeNextand uses the built-infetch). - TypeScript ≥ 5.5 (optional — works fine in plain JS too).
npm install
npm run buildimport { FitDaysClient } from 'fitdays-api'
const client = new FitDaysClient({ region: 'us' })
// Login (the plaintext password is hashed internally)
const session = await client.login('foo@example.com', 'my-password')
console.log('uid:', session.uid)
// Pull ~6 years of data
const sync = await client.syncAll()
console.log(`${sync.data.weight_list.length} measurements`)
// Custom window (unix-seconds; startTime is the UPPER bound)
const now = Math.floor(Date.now() / 1000)
const week = await client.syncFromServer({
endTime: now - 7 * 86400,
startTime: now,
})new FitDaysClient(opts?: ClientOptions)| option | default | description |
|---|---|---|
region |
'us' |
'us' | 'eu' | 'cn' — picks the API host |
baseUrl |
derived from region |
overrides the host (useful for tests or new hosts) |
country |
'CN' for cn, otherwise 'US' |
sent as the country query param |
language |
'zh' for cn, otherwise 'en' |
sent as the language query param |
deviceModel |
'AndroidSDKbuiltforarm64-6.0' |
sent as the device_model query param |
clientId |
generated (uppercase MD5 of UUID) | identifies the client "device" |
fetchImpl |
globalThis.fetch |
inject for tests or to use undici / node-fetch |
client.baseUrl string — auto-updated on a 302 redirect
client.clientId readonly string
client.country readonly string
client.deviceModel readonly string
client.language readonly string
client.session Session | null — populated by login
To "log in" without calling login() (e.g. reusing a saved token):
client.session = { token: '...', uid: 12345, refreshToken: '...' }
interface FitDaysClient {
login(email: string, password: string): Promise<Session>
loginWithPhone(phone: string, password: string): Promise<Session>
syncFromServer(opts: { endTime: number, startTime: number }): Promise<ApiResponse<SyncFromServerData>>
syncAll(): Promise<ApiResponse<SyncFromServerData>>
request<T>(path: string, body?: unknown): Promise<ApiResponse<T>>
}Every method throws FitDaysApiError when the response carries an error code.
Low-level helper for any endpoint that uses the same signing scheme. It pulls the token and client_id from the current session automatically:
const res = await client.request<{ items: unknown[] }>('api/some/endpoint', { foo: 1 })
Handles code: 302 redirects automatically (updates client.baseUrl and replays the request).
Heads-up: by server convention, startTime is the most-recent bound and endTime is the oldest (both in unix-seconds). syncAll() is shorthand for [now - ~6 years, now].
The SDK automatically parses weight_list[i].ext_data, including null values. Missing response lists are returned as empty arrays.
import type {
SyncFromServerData,
WeightRecord,
WeightExtData,
HeightRecord,
Device,
BindDevice,
AccountInfo,
User,
} from 'fitdays-api'| type | content |
|---|---|
SyncFromServerData |
root object with account and response lists |
AccountInfo |
account record (uid, email, server_id, configs, …) + sub-users |
User |
sub-user (suid, nickname, sex, birthday, height, photo) |
Device / BindDevice |
physical device and its account binding |
WeightRecord |
one body-composition measurement |
WeightExtData |
parsed shape of present ext_data inside each WeightRecord |
HeightRecord |
one height measurement |
Lists with too few observed samples stay typed as Record<string, unknown>[] (RulerRecord, BalanceRecord, GravityRecord, ImpedanceRecord, HrRecord, SkipRecord, Product). Once you capture a populated response, tighten them in src/types/sync.ts.
If you need the un-parsed shape, use WeightRecordRaw / SyncFromServerDataRaw (same fields, but conditional fields may be absent or null). The helpers parseWeightRecord and parseSyncFromServerData are exported as well.
FitDaysApiError extends Error is thrown when:
- The API returns a
codeother than0or200. - The response body is not JSON.
- The server returned a redirect (
302) without adomainfield. - A successful login response is missing
tokenanduid.
import { FitDaysApiError } from 'fitdays-api'
try {
await client.login(email, password)
} catch (err) {
if (err instanceof FitDaysApiError) {
console.error('code', err.code, 'response', err.response)
} else {
throw err
}
}const client = new FitDaysClient({ region: 'us' })
client.session = JSON.parse(await readFile('session.json', 'utf8'))
await client.syncAll()
const sync = await client.syncAll()
const activeUid = sync.data.account.active_suid
const meAndRecent = sync.data.weight_list
.filter((w) => w.suid === activeUid && w.is_deleted === 0)
.sort((a, b) => b.measured_time - a.measured_time)
const verboseFetch: typeof fetch = (input, init) => {
console.log('→', input)
return fetch(input, init)
}
const client = new FitDaysClient({ fetchImpl: verboseFetch })
src/
├── client/
│ └── fitdays-client.ts FitDaysClient
├── constants/
│ ├── api.ts APP_VER, REGION_HOSTS, FULL_SYNC_WINDOW_SECONDS, …
│ └── sign.ts SIGN_SECRET, PASSWORD_SALT
├── errors/
│ └── api-error.ts FitDaysApiError
├── scripts/
│ ├── test-sync.ts run a real sync (reads .env)
│ └── verify-sign.ts checks the signer against a known vector
├── tests/ unit tests (node:test)
├── types/
│ ├── api.ts ApiResponse, LoginData
│ ├── client.ts Region, ClientOptions, Session
│ ├── sign.ts SignParams
│ └── sync.ts syncFromServer response types
├── utils/
│ ├── crypto.ts md5Hex, hashPassword, newClientId, newRequestId
│ ├── encode.ts javaUrlEncode
│ ├── parse-sync.ts parseWeightRecord, parseSyncFromServerData
│ └── sign.ts buildSign
└── fitdays-api.ts entry point — re-exports the public API
| script | what it does |
|---|---|
npm run build |
compiles with tsc to dist/ |
npm run lint |
ESLint (typescript-eslint + perfectionist + stylistic) with --fix |
npm test |
compiles + runs the native Node tests with coverage. Fails below 90% lines/funcs or 80% branches |
npm run verify-sign |
checks the signer against a known vector |
npm run test:sync |
full login + sync against the real server (reads FITDAYS_EMAIL / FITDAYS_PASSWORD from .env); writes the response to sync-response.json |
To use test:sync, create a .env file (already in .gitignore):
FITDAYS_EMAIL=foo@example.com
FITDAYS_PASSWORD=my-password
This SDK is not affiliated with or endorsed by FitDays / Icomon. Use at your own risk and respect the service's ToS.
This SDK is built and maintained on personal time, on hardware bought for the purpose. If it is useful to you, consider sponsoring the work — it keeps the devices, the testing and the releases coming.