Weekly personal review: Todoist + Calendar → Notion
Completed tasks plus meeting vs solo hours, filed as one Notion weekly review.
Once a week, pull Todoist completions and your meeting vs solo hours into one editable Notion review page. Todoist's Productivity view is charts you open — no document, no calendar cross-check. Google Calendar Time Insights is Workspace/school only, an in-app panel with no export or API, never beside task counts — this covers personal Gmail too. Optional Habit Tracker notes land as a read-only Habit notes property. Your own week, not a team capacity rollup: events.list on your primary calendar splits meetings (attendees ≥ 2) from solo blocks.
How it flows
- 01
Weekly trigger fires
Run once at a fixed point on the week boundary (Sunday evening or Monday morning). Use the primary calendar timezone for the window so Todoist, Calendar, and the Notion week label share one clock.
- 02
Completed tasks pulled from Todoist
GET /api/v1/tasks/completed/by_completion_date for this week's since/until. Sum item count and keep titles for the review page.
- 03
Primary calendar split into meetings vs solo blocks
events.list on primary with eventTypes=default and singleEvents=true. Default rule: attendees ≥ 2 → meeting hours; otherwise (no/0/1 attendees) → solo/focus hours. Sum both.
- 04
Optional — Habit Tracker notes copied
If enabled, read each tracked habit task's description and latest comment; hold them as a read-only subsection for the Notion page. Skip entirely when the extension is not in use.
- 05
Last week's Notion page loaded for deltas
Query the Weekly Review data source for the most recent page and compute week-over-week deltas on tasks completed and meeting hours.
- 06
This week's review page instantiated
POST /v1/pages with the template id and all computed properties (counts, hours, deltas, and optional Habit notes rich_text/Text if that module is on). One editable page per week — habit copy stays in properties, not a post-create block PATCH.
Set up each app
Work through these in order — later apps usually need a token or an id from an earlier one.
Todoist
This week's completed tasks (and optional habit streaks)
- 01
Copy a read-only API token
app.todoist.com → avatar → Settings → Integrations → Developer → copy your personal API token. This workflow only reads completed tasks (and, if you enable the optional habit section, task descriptions and comments), so request or rely on `data:read` — nothing writes back to Todoist.
- 02
Pull completions by completion date, and know the free-plan window
Use GET /api/v1/tasks/completed/by_completion_date with since/until set to this week's window — that filters on when you checked the task off, which is what a weekly review means by "what I finished." The sibling endpoint by_due_date filters on the original due date instead and will miss early finishes and include overdue catch-ups, so leave it alone here. Call /api/v1/ only: REST v2 and Sync v9 both return 410 Gone. On Beginner (free), Todoist's Activity history keeps only one week of history — if this job runs late, older completions can already be gone, so keep the weekly trigger on time or upgrade to Pro for full history.
GET /api/v1/tasks/completed/by_completion_datecurl -s -G "https://api.todoist.com/api/v1/tasks/completed/by_completion_date" \ -H "Authorization: Bearer $TODOIST_TOKEN" \ -d since=2026-08-03T00:00:00 \ -d until=2026-08-10T00:00:00 \ -d limit=50 - 03
Optional — Habit Tracker read-only copy
Skip this unless you already use Todoist's first-party Habit Tracker (Settings → Integrations → Browse; available on Beginner/Pro/Business). Habit Tracker already keeps streak progress in the task description and completion history in task comments — this module does not reinvent streaks; it only copies those existing fields into the Notion review as a read-only section. Note the task ids you track, and plan to read each task's description plus GET /api/v1/comments?task_id=… for the latest comment. Do not write your own notes into a tracked task's description — the extension overwrites it.
GET /api/v1/comments?task_id=…curl -s -G "https://api.todoist.com/api/v1/comments" \ -H "Authorization: Bearer $TODOIST_TOKEN" \ -d task_id=6XR4GqQQCW6Gv9h4
Google Calendar
Meeting hours vs solo/focus blocks on your primary calendar
- 01
Create an OAuth client with the owned-events scope
Google Cloud Console → APIs & Services → Credentials → create an OAuth client. Request only `https://www.googleapis.com/auth/calendar.events.owned.readonly` — it reads events on calendars you own, which is enough for primary and tighter than the broader `calendar.events.readonly` that also covers calendars others shared with you. The freebusy-only scope is not enough here: freeBusy returns busy intervals with no titles or attendees, so it cannot split meetings from solo blocks.
- 02
Fix the week window to your primary calendar timezone
Derive since/until (and Calendar timeMin/timeMax) from the timezone of your primary calendar — one timezone for Todoist completions, Calendar events, and the Notion week label, so the three numbers line up. Default classification (configurable): attendees.length ≥ 2 (you usually appear in attendees, so two means at least one other person) → meeting; otherwise (missing attendees, length 0, or length 1 = only you) → solo/focus block. Tune the threshold later if you want to exclude declined guests via responseStatus.
- 03
Verify events.list on primary for this week
GET calendars/primary/events with singleEvents=true (required so recurring series expand into this week's instances), orderBy=startTime, and eventTypes=default to exclude focusTime, outOfOffice, birthday, and workingLocation. If you also want a separate Focus Time column, make a second call with eventTypes=focusTime rather than mixing types in one response. Sum durations after classifying by the attendee rule above.
GET /calendar/v3/calendars/primary/eventscurl -s -G "https://www.googleapis.com/calendar/v3/calendars/primary/events" \ -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" \ -d timeMin=2026-08-03T00:00:00-04:00 \ -d timeMax=2026-08-10T00:00:00-04:00 \ -d singleEvents=true \ -d orderBy=startTime \ -d eventTypes=default
Notion
Weekly Review database — editable page per week
- 01
Create an internal integration
app.notion.com/my-integrations → New integration. Capabilities: Insert content and Read content — you write a new weekly page and may read last week's page for week-over-week deltas. Leave user information at "No user information"; this is a single-player workflow with no @-mentions. Copy the token.
- 02
Build the Weekly Review database and template
Create a "Weekly Review" database with properties the API will write by name, for example: Name (Title), Week of (Date), Tasks completed (Number), Meeting hours (Number), Solo/focus hours (Number), vs last week (Text), and optionally Habit notes (Text) if you enable the Habit Tracker module — put the copied description/latest-comment text there on create so you never race the template's async body fill-in. In the database, New → dropdown arrow → New template, and sketch fixed reflection prompts (What shipped / Where time went / Next week). The template only produces that skeleton — it cannot pull Todoist or Calendar — so every computed number must go in properties on create.
- 03
Share the database and record the data source id
Open the database → ••• → Add connections → pick the integration. Then GET /v1/databases/{database_id} with Notion-Version: 2026-03-11 and store data_sources[0].id — that is not the id in the URL. Look up the template id with GET /v1/data_sources/{data_source_id}/templates?name=… rather than hardcoding it.
GET data source templatesGET https://api.notion.com/v1/data_sources/{data_source_id}/templates?name=Weekly%20Review Authorization: Bearer $NOTION_INTEGRATION_TOKEN Notion-Version: 2026-03-11 - 04
Test page create and last-week query
POST /v1/pages with parent.data_source_id, the computed properties, and template.template_id. Put numbers in properties in that same call — template body content is applied asynchronously, so do not PATCH blocks immediately after create or you race Notion's fill-in. For week-over-week copy, POST /v1/data_sources/{data_source_id}/query sorted by your Week of (or equivalent) date descending with page_size 1, then subtract last week's Tasks completed / Meeting hours from this week's.
POST /v1/pages with template{ "parent": { "data_source_id": "<WEEKLY_REVIEW_DATA_SOURCE_ID>" }, "properties": { "Name": { "title": [{ "text": { "content": "Week of Aug 3" } }] }, "Week of": { "date": { "start": "2026-08-03" } }, "Tasks completed": { "number": 18 }, "Meeting hours": { "number": 6.5 }, "Solo/focus hours": { "number": 4 }, "vs last week": { "rich_text": [{ "text": { "content": "+3 tasks, −1.0 meeting hours" } }] } }, "template": { "type": "template_id", "template_id": "<WEEKLY_REVIEW_TEMPLATE_ID>" } }