# Forgejo Time Tracking API

Base URL: `https://git.cleverthis.com/api/v1`

All endpoints require authentication via `Authorization: token ${FORGEJO_PAT}` header.

**Note:** Time tracking must be enabled in the repository settings for these endpoints to work. When enabled, users can log time spent on issues and use the stopwatch feature.

---

## TrackedTime Object Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Tracked time entry ID |
| `time` | integer | Time in seconds |
| `user_id` | integer | User ID who tracked the time |
| `user_name` | string | Username who tracked the time |
| `issue_id` | integer | Internal issue ID |
| `issue` | object | Issue summary (if included) |
| `created` | string | ISO 8601 timestamp when the time was logged |

---

## Issue Time Tracking

### List Tracked Times on an Issue

```
GET /repos/{owner}/{repo}/issues/{index}/times
```

Returns all tracked time entries for the specified issue.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |
| `user` | string | query | Filter by username |
| `since` | string | query | Only show entries after this date (ISO 8601) |
| `before` | string | query | Only show entries before this date (ISO 8601) |
| `page` | integer | query | Page number (1-based). Default: `1` |
| `limit` | integer | query | Page size |

#### Response

Returns `200` with an array of TrackedTime objects.

#### Examples

```bash
# List all tracked times on issue #42
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"

# List with human-readable summary
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times" \
  | jq '[.[] | {
      id,
      user: .user_name,
      hours: (.time / 3600 | floor),
      minutes: ((.time % 3600) / 60 | floor),
      seconds: (.time % 60),
      created: .created
    }]'

# Get total tracked time in seconds
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times" \
  | jq '[.[].time] | add'

# Get total tracked time formatted as hours
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times" \
  | jq '([.[].time] | add) / 3600 | . * 100 | round / 100'

# Filter by user
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times?user={username}"

# Filter by date range
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times?since=2024-01-01T00:00:00Z&before=2024-07-01T00:00:00Z"
```

---

### Add Tracked Time to an Issue

```
POST /repos/{owner}/{repo}/issues/{index}/times
```

Logs time spent on an issue.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

#### Request Body — `AddTimeOption`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `time` | integer | **Yes** | Time to add in seconds |
| `created` | string | No | Override the creation timestamp (ISO 8601). Useful for logging past work |
| `user_name` | string | No | Username to attribute the time to (admin only, defaults to authenticated user) |

#### Response

Returns `200` with the created TrackedTime object.

#### Examples

```bash
# Log 2 hours (7200 seconds)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "time": 7200
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"

# Log 30 minutes (1800 seconds)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "time": 1800
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"

# Log 1 hour 15 minutes with a custom date
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "time": 4500,
    "created": "2024-06-10T14:00:00Z"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"

# Log time on behalf of another user (admin only)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "time": 3600,
    "user_name": "{username}"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"
```

---

### Reset (Delete All) Tracked Time on an Issue

```
DELETE /repos/{owner}/{repo}/issues/{index}/times
```

Deletes **all** tracked time entries on the specified issue. This is irreversible.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

#### Response

Returns `204` (No Content) on success.

#### Example

```bash
# Reset all tracked time on issue #42
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"
```

---

### Delete a Specific Time Entry

```
DELETE /repos/{owner}/{repo}/issues/{index}/times/{id}
```

Deletes a single tracked time entry.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |
| `id` | integer | path | **Required.** Time entry ID |

#### Response

Returns `204` (No Content) on success.

#### Example

```bash
# Delete time entry #567 from issue #42
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times/567"
```

---

## Repository-Level Time Tracking

### List All Tracked Times in a Repository

```
GET /repos/{owner}/{repo}/times
```

Returns all tracked time entries across all issues in the repository.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `user` | string | query | Filter by username |
| `since` | string | query | Only show entries after this date (ISO 8601) |
| `before` | string | query | Only show entries before this date (ISO 8601) |
| `page` | integer | query | Page number (1-based). Default: `1` |
| `limit` | integer | query | Page size |

#### Response

Returns `200` with an array of TrackedTime objects.

#### Examples

```bash
# List all tracked times in repo
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/times"

# Get total time spent on the repo (in hours)
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/times?limit=50" \
  | jq '([.[].time] | add) / 3600 | . * 100 | round / 100'

# Time tracked by a specific user
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/times?user={username}"

# Time tracked this month
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/times?since=2024-06-01T00:00:00Z"

# Time breakdown by user
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/times?limit=50" \
  | jq 'group_by(.user_name) | map({
      user: .[0].user_name,
      total_seconds: ([.[].time] | add),
      total_hours: (([.[].time] | add) / 3600 | . * 100 | round / 100),
      entries: length
    }) | sort_by(-.total_seconds)'
```

---

### List Tracked Times by User in a Repository

```
GET /repos/{owner}/{repo}/times/{user}
```

Returns tracked time entries for a specific user in the repository.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `user` | string | path | **Required.** Username |

#### Response

Returns `200` with an array of TrackedTime objects.

#### Example

```bash
# List Alice's tracked time in the repo
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/times/{username}"
```

---

## Issue Deadlines

### Set or Update a Deadline

```
POST /repos/{owner}/{repo}/issues/{index}/deadline
```

Sets or updates the deadline (due date) for an issue.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

#### Request Body — `EditDeadlineOption`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `due_date` | string | **Yes** | Deadline in ISO 8601 format. Set to empty string or null to remove the deadline |

#### Response

Returns `201` with an IssueDeadline object containing the `due_date`.

#### Examples

```bash
# Set deadline for issue #42
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "due_date": "2025-03-15T00:00:00Z"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/deadline"

# Remove deadline
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "due_date": null
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/deadline"
```

---

## Stopwatch

The stopwatch feature allows real-time time tracking. A user can start a stopwatch on an issue, and when they stop it, the elapsed time is automatically logged.

**Important:** A user can only have one running stopwatch at a time. Starting a stopwatch on a new issue will fail if another stopwatch is already running — you must stop the existing one first.

### Delete a Stopwatch

```
DELETE /repos/{owner}/{repo}/issues/{index}/stopwatch/delete
```

Deletes (cancels) the running stopwatch without logging the time.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

#### Response

Returns `204` (No Content) on success. Returns `409` if no stopwatch is running.

#### Example

```bash
# Cancel stopwatch on issue #42 (time is discarded)
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/delete"
```

---

### Start a Stopwatch

```
POST /repos/{owner}/{repo}/issues/{index}/stopwatch/start
```

Starts a stopwatch timer on the issue for the authenticated user.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

#### Response

Returns `201` on success. Returns `409` if a stopwatch is already running (on any issue).

#### Example

```bash
# Start stopwatch on issue #42
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/start"
```

---

### Stop a Stopwatch

```
POST /repos/{owner}/{repo}/issues/{index}/stopwatch/stop
```

Stops the running stopwatch and logs the elapsed time as a tracked time entry.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

#### Response

Returns `201` on success. Returns `409` if no stopwatch is running on this issue.

#### Example

```bash
# Stop stopwatch on issue #42 (time is logged automatically)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/stop"
```

---

## User-Level Endpoints

### Current User's Tracked Times

```
GET /user/times
```

Returns all tracked time entries for the authenticated user across all repositories.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `since` | string | query | Only show entries after this date (ISO 8601) |
| `before` | string | query | Only show entries before this date (ISO 8601) |
| `page` | integer | query | Page number (1-based). Default: `1` |
| `limit` | integer | query | Page size |

#### Response

Returns `200` with an array of TrackedTime objects.

#### Examples

```bash
# List all my tracked times
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/times"

# My tracked times this week
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/times?since=$(date -d 'last monday' -u +%Y-%m-%dT00:00:00Z)"

# My total hours tracked
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/times?limit=50" \
  | jq '([.[].time] | add) / 3600 | . * 100 | round / 100'

# My time breakdown by issue
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/times?limit=50" \
  | jq 'group_by(.issue_id) | map({
      issue_id: .[0].issue_id,
      total_hours: (([.[].time] | add) / 3600 | . * 100 | round / 100),
      entries: length
    }) | sort_by(-.total_hours)'
```

---

### Current User's Running Stopwatches

```
GET /user/stopwatches
```

Returns all running stopwatches for the authenticated user.

#### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `page` | integer | query | Page number (1-based). Default: `1` |
| `limit` | integer | query | Page size |

#### Response

Returns `200` with an array of StopWatch objects.

#### StopWatch Object Fields

| Field | Type | Description |
|-------|------|-------------|
| `created` | string | ISO 8601 timestamp when the stopwatch was started |
| `seconds` | integer | Seconds elapsed since stopwatch started |
| `duration` | string | Human-readable duration string |
| `issue_index` | integer | Issue index number |
| `issue_title` | string | Issue title |
| `repo_name` | string | Repository full name (owner/repo) |
| `repo_owner_name` | string | Repository owner name |

#### Examples

```bash
# List my running stopwatches
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/stopwatches"

# Show running stopwatch details
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/stopwatches" \
  | jq '[.[] | {
      repo: .repo_name,
      issue: .issue_index,
      title: .issue_title,
      running_for: .duration,
      started: .created
    }]'
```

---

## Common Patterns

### Full stopwatch workflow

```bash
OWNER="{owner}"
REPO="{repo}"
INDEX="{index}"

# 1. Check if a stopwatch is already running
RUNNING=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/user/stopwatches" \
  | jq 'length')

if [ "$RUNNING" -gt 0 ]; then
  echo "A stopwatch is already running. Stop it first."
  curl -s \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/user/stopwatches" \
    | jq '.[0] | "Running on " + .repo_name + " #" + (.issue_index|tostring) + " for " + .duration'
else
  # 2. Start stopwatch
  curl -s -X POST \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/issues/${INDEX}/stopwatch/start"
  echo "Stopwatch started on issue #${INDEX}"
fi

# 3. Later... stop the stopwatch (time is auto-logged)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/issues/${INDEX}/stopwatch/stop"
echo "Stopwatch stopped, time logged."
```

### Generate a time report for a repository

```bash
OWNER="{owner}"
REPO="{repo}"

echo "=== Time Report for ${OWNER}/${REPO} ==="
echo ""

# Total time
TOTAL=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/times?limit=50" \
  | jq '([.[].time] | add // 0) / 3600 | . * 100 | round / 100')
echo "Total hours tracked: ${TOTAL}h"
echo ""

# Time by user
echo "By user:"
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/times?limit=50" \
  | jq -r 'group_by(.user_name) | map({
      user: .[0].user_name,
      hours: (([.[].time] | add) / 3600 | . * 100 | round / 100)
    }) | sort_by(-.hours) | .[] | "  \(.user): \(.hours)h"'
```

### Log time using human-friendly input

```bash
# Helper: convert "1h30m" style input to seconds
time_to_seconds() {
  local input="$1"
  local hours=0 minutes=0 seconds=0
  hours=$(echo "$input" | grep -oP '\d+(?=h)' || echo 0)
  minutes=$(echo "$input" | grep -oP '\d+(?=m)' || echo 0)
  seconds=$(echo "$input" | grep -oP '\d+(?=s)' || echo 0)
  echo $(( (hours * 3600) + (minutes * 60) + seconds ))
}

# Log "2h30m" to issue #42
SECONDS_TO_LOG=$(time_to_seconds "2h30m")
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d "{\"time\": ${SECONDS_TO_LOG}}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/times"
```
