DoneThat

Reports

Pull aggregated activity rows for a date range at activity, day, task, or week resolution.

POST/reportScopes:reports:read

The reports endpoint returns aggregated work data for a date range, at the resolution you ask for. It is the same data that powers the in-app stats views.

POST https://api.donethat.ai/report

Required scope: reports:read

Request body

{
  "dateRange": {
    "from": 1767225600000,
    "to":   1768608000000
  },
  "aggregationLevel": "day",
  "projectIds": ["..."],
  "teamIds": ["..."],
  "includeCategories": true,
  "includeProjects": true,
  "sort": "desc"
}
FieldTypeRequiredNotes
dateRange.fromnumberYes*Unix milliseconds. *Not required when using the empty body default.
dateRange.tonumberYes*Unix milliseconds. Must be greater than from. *Not required when using the empty body default.
aggregationLevelstringYes*One of activity, day, task, week. minute is accepted as a legacy alias for activity. *Defaults to day with the empty body.
projectIdsstring[]NoProject ids you own or can access. Unknown ids return 400.
teamIdsstring[]NoTeam ids you are an active member of. Only valid with day, task, or week (not activity). Returns member activity including user / user_email on rows when applicable.
includeCategoriesbooleanNoDefaults to true.
includeProjectsbooleanNoDefaults to true.
sortstringNoasc or desc. When omitted, rows are ordered by stable row id ascending.

Empty body default

If you send an empty JSON body, the endpoint returns a useful default report:

  • dateRange: the last 7 days.
  • aggregationLevel: day.
  • sort: desc.

Range limits

  • activity resolution (per tracked minute / screenshot) - up to 90 days.
  • day, task, and week resolution - up to 365 days.
  • week aggregation only processes full weeks; partial weeks at the edges are dropped.

Response

{
  "success": true,
  "rowCount": 42,
  "rows": [
    {
      "id": "day:2026-01-01",
      "date": "2026-01-01",
      "duration": 330,
      "duration_relative": 82.5,
      "projects": [
        { "name": "My Project", "minutes": 240 }
      ],
      "categories": [
        { "name": "Focus", "minutes": 210 }
      ],
      "tasks": [
        { "title": "Fix bug", "duration": 90 }
      ],
      "comment": "Shipped the bug fix and reviewed onboarding work."
    }
  ]
}

This example shows a day-level row. Row fields vary by aggregationLevel; activity-level rows include fields like timestamp, timestampIso, time, task, headline, and description. Row id values use an activity: prefix when you request aggregationLevel: "activity", and minute: when you use the legacy minute alias.

Every row includes a stable id for deduplication. Minute rows also include timestampIso, the ISO 8601 UTC companion to timestamp.

Row objects use snake_case field names (see Overview).

When polling for new data, map rows and use each row's id as the deduplication key. Prefer aggregationLevel: "day" and narrow dateRange windows to stay within your client's HTTP timeouts.

Example

curl -X POST "https://api.donethat.ai/report" \
     -H "Content-Type: application/json" \
     -H "x-api-key: YOUR_API_KEY" \
     -d '{
       "dateRange": { "from": 1767225600000, "to": 1768608000000 },
       "aggregationLevel": "day",
       "includeCategories": true,
       "sort": "desc"
     }'