Skip to main content
This is the core gamification endpoint. Calling it does three things atomically:
  1. Creates a TaskCompletion record.
  2. Adds the task’s points value to the user’s total points.
  3. If this is the user’s first completion of the day, increments current_streak and updates max_streak if the new streak exceeds the previous record.
The streak counter increments once per calendar day, on the first task completion of that day — regardless of which task is completed. Completing multiple tasks on the same day does not increment the streak more than once.
The streak counter only goes up at completion time. Whether yesterday’s streak should be broken is evaluated by an overnight cron job, not in real time. This means current_streak may appear higher than expected until the cron runs. If you need to check whether a streak is still valid, compare current_streak against the user’s last-completion timestamp.

Request body

task_id
number
required
ID of the task being completed.

Response

201 Created — Returns the new task completion record.
id
number
required
Auto-incremented unique identifier for this completion record.
task_id
number
required
ID of the task that was completed.
user_id
string
required
UUID of the user who completed the task.
completed_at
string
required
ISO 8601 timestamp set to the server time at the moment of completion.

Side effects

The following changes are applied to the user’s profile as part of the same request:
FieldChange
pointsIncreased by task.points.
current_streakIncremented by 1 if this is the first completion today; unchanged otherwise.
max_streakUpdated to current_streak if the new streak exceeds the stored record.

Streak logic

The exact logic executed in taskCompletion.service.ts:
const todayCompletions = await taskCompletionRepository.findByUserIdToday(userId);
const hasCompletedToday = todayCompletions.length > 0;

// completion record is written here

if (!hasCompletedToday) {
    newStreak += 1;
    if (newStreak > maxStreak) {
        maxStreak = newStreak;
    }
}

await userRepository.update(userId, {
    points: user.points + task.points,
    current_streak: newStreak,
    max_streak: maxStreak
});
findByUserIdToday queries completions where completed_at >= CURRENT_DATE AND completed_at < CURRENT_DATE + INTERVAL '1 day', so “today” is always the server’s current calendar date in UTC.
There is no unique constraint on (user_id, task_id, date) in the database schema. The same task can be completed multiple times on the same day. Each call creates a new completion record and awards points again. Only the streak counter is bounded to once per day.

Errors

StatusCondition
404No task found with the given task_id.
400The task exists but belongs to a different user.
401Missing or invalid authentication token.
curl --request POST \
  --url http://localhost:3000/api/task-completions \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
    "task_id": 42
  }'
{
  "id": 301,
  "task_id": 42,
  "user_id": "a3f1c2d4-0e5b-4a7f-b891-2c3d4e5f6a7b",
  "completed_at": "2026-03-17T14:23:05.000Z"
}

Build docs developers (and LLMs) love