Privacy-focused time tracking for Android, with a REST API for integrations
A simple, privacy-focused time tracking app for Android.
TimeTracker provides a REST API for third-party integrations. Use it to check your tracking status, toggle tracking, query time entries, and read progress metrics from external tools, scripts, or smart home automations.
Base URL:
https://europe-west1-timetracker-22fbb.cloudfunctions.net/api
All endpoints require a Firebase ID token in the Authorization header:
Authorization: Bearer <FIREBASE_ID_TOKEN>
To obtain a token, sign in with Google via Firebase Auth and call getIdToken() on the Firebase user object.
Error responses:
| Status | Body | Cause |
|---|---|---|
401 |
{ "error": "Missing or malformed Authorization header" } |
Header absent or not in Bearer <token> format |
401 |
{ "error": "Invalid or expired token" } |
Token failed verification |
GET /tracking/statusReturns the current tracking state.
Response:
{
"isTracking": false,
"startTime": 0
}
| Field | Type | Description |
|---|---|---|
isTracking |
boolean |
Whether the timer is currently running |
startTime |
number |
Epoch milliseconds when tracking started, or 0 if idle |
Calculating elapsed time: If
isTrackingistrue, compute elapsed milliseconds asDate.now() - startTime. The API intentionally does not return elapsed time because it would be stale by the time you read it.
POST /tracking/toggleToggles the tracking state. If idle, starts tracking. If tracking, stops and creates a time entry.
Request body: None required.
Response when starting:
{
"isTracking": true,
"startTime": 1738857600000
}
Response when stopping:
{
"isTracking": false,
"startTime": 0,
"entry": {
"id": "abc123",
"startTime": 1738857600000,
"endTime": 1738861200000,
"type": "WORK",
"duration": 3600000
}
}
| Field | Type | Description |
|---|---|---|
isTracking |
boolean |
New tracking state after toggle |
startTime |
number |
Start time if now tracking, 0 if stopped |
entry |
object |
Only present when stopping. The created time entry |
entry.id |
string |
Firestore document ID of the new entry |
entry.startTime |
number |
Epoch ms when the session began |
entry.endTime |
number |
Epoch ms when the session ended |
entry.type |
string |
Always "WORK" |
entry.duration |
number |
Duration in milliseconds |
GET /progressReturns pre-computed progress metrics. This data is automatically recalculated by a Cloud Function whenever time entries, tracking state, or settings change.
Response:
{
"daily": {
"hoursTracked": 3.5,
"goal": 8.0,
"progress": 0.4375
},
"weekly": {
"hoursTracked": 22.0,
"goal": 37.5,
"progress": 0.5867
},
"monthly": {
"hoursTracked": 88.0,
"goal": 160.0,
"progress": 0.55
},
"tracking": {
"isTracking": true,
"startTime": 1738857600000
},
"overtimeCarryover": 2.5,
"weeksHistory": [],
"lastUpdated": 1738861200000
}
| Field | Type | Description |
|---|---|---|
daily |
ProgressMetric |
Today’s progress |
weekly |
ProgressMetric |
This week’s progress (with overtime adjustment) |
monthly |
ProgressMetric |
This month’s progress |
tracking |
object |
Current tracking state |
overtimeCarryover |
number |
Hours of overtime carried from previous week |
weeksHistory |
array |
See GET /history for the shape |
lastUpdated |
number\|null |
Epoch ms when progress was last recomputed |
ProgressMetric:
| Field | Type | Description |
|---|---|---|
hoursTracked |
number |
Hours tracked in this period (excludes active session) |
goal |
number |
Target hours for this period |
progress |
number |
Ratio hoursTracked / goal, clamped to 0.0-1.0 |
Live progress during tracking: The
hoursTrackedvalues do not include the currently active session. To display live progress, add(Date.now() - tracking.startTime) / 3600000to thehoursTrackedvalues whentracking.isTrackingistrue.
Error: Returns 404 with { "error": "No progress data found. Start tracking to generate data." } if no computed data exists yet.
GET /entriesReturns time entries with optional filtering and pagination.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
start |
number |
— | Filter entries with startTime >= start (epoch ms) |
end |
number |
— | Filter entries with startTime <= end (epoch ms) |
limit |
number |
500 |
Max entries to return (capped at 1000) |
Example:
GET /entries?start=1738368000000&end=1738972800000&limit=100
Response:
{
"entries": [
{
"id": "abc123",
"startTime": 1738857600000,
"endTime": 1738861200000,
"type": "WORK",
"duration": 3600000
}
],
"count": 1
}
| Field | Type | Description |
|---|---|---|
entries |
array |
Time entries, sorted by startTime descending |
entries[].id |
string |
Firestore document ID |
entries[].startTime |
number |
Epoch ms |
entries[].endTime |
number |
Epoch ms |
entries[].type |
string |
Entry type (e.g., "WORK") |
entries[].duration |
number |
Duration in milliseconds |
count |
number |
Number of entries returned |
GET /historyReturns aggregated weekly history with overtime tracking.
Response:
{
"weeksHistory": [
{
"year": 2026,
"week": 6,
"weekStartMillis": 1738540800000,
"weekEndMillis": 1739145599999,
"totalDuration": 144000000,
"standardWeeklyGoal": 40,
"effectiveWeeklyGoal": 37.5,
"overtimeFromPreviousWeek": 2.5,
"overtimeGenerated": 1.0,
"dailyTotals": {
"2": 28800000,
"3": 28800000,
"4": 28800000,
"5": 28800000,
"6": 28800000
},
"dailyGoals": {
"2": 8.0,
"3": 8.0,
"4": 8.0,
"5": 8.0,
"6": 8.0
}
}
],
"overtimeCarryover": 1.0
}
| Field | Type | Description |
|---|---|---|
weeksHistory |
array |
Weeks sorted by most recent first |
weeksHistory[].year |
number |
Year |
weeksHistory[].week |
number |
ISO week number |
weeksHistory[].weekStartMillis |
number |
Epoch ms of week start |
weeksHistory[].weekEndMillis |
number |
Epoch ms of week end |
weeksHistory[].totalDuration |
number |
Total tracked time in milliseconds |
weeksHistory[].standardWeeklyGoal |
number |
Base weekly goal in hours |
weeksHistory[].effectiveWeeklyGoal |
number |
Goal after overtime deduction |
weeksHistory[].overtimeFromPreviousWeek |
number |
Overtime hours carried in |
weeksHistory[].overtimeGenerated |
number |
Overtime hours generated this week |
weeksHistory[].dailyTotals |
object |
Tracked ms per day. Keys are day-of-week ("1"=Sunday, "2"=Monday, …, "7"=Saturday) |
weeksHistory[].dailyGoals |
object |
Goal hours per day. Same key format as dailyTotals |
overtimeCarryover |
number |
Current overtime carryover in hours |
All endpoints return JSON error objects on failure:
| Status | Body | Cause |
|---|---|---|
401 |
{ "error": "..." } |
Authentication failed |
404 |
{ "error": "..." } |
Resource not found |
500 |
{ "error": "Internal server error" } |
Unexpected server error |
# Get a Firebase ID token (you'd normally get this from your app)
TOKEN="your-firebase-id-token"
# Check tracking status
curl -H "Authorization: Bearer $TOKEN" \
https://europe-west1-timetracker-22fbb.cloudfunctions.net/api/tracking/status
# Start/stop tracking
curl -X POST -H "Authorization: Bearer $TOKEN" \
https://europe-west1-timetracker-22fbb.cloudfunctions.net/api/tracking/toggle
# Get today's progress
curl -H "Authorization: Bearer $TOKEN" \
https://europe-west1-timetracker-22fbb.cloudfunctions.net/api/progress
# Get this week's entries
curl -H "Authorization: Bearer $TOKEN" \
"https://europe-west1-timetracker-22fbb.cloudfunctions.net/api/entries?start=1738540800000&end=1739145599999"
# Get weekly history
curl -H "Authorization: Bearer $TOKEN" \
https://europe-west1-timetracker-22fbb.cloudfunctions.net/api/history
TimeTracker is privacy-focused: no ads, no analytics and no tracking. Your data is stored in Google Firebase so it can sync and work offline, and only your account can access it. Signing in with Google is optional, and you can continue without an account instead.
Questions? Email [email protected]