Guides

Sync project dates and checklists to a spreadsheet

Copy phase dates, milestones and checklist items with people and due dates from Monument into a spreadsheet, and keep dates current with webhooks.

This guide copies project dates and checklists from Monument into a spreadsheet. Your own script then reads the sheet, for example to make Google Tasks. Data goes one way: from Monument to the sheet. Changes in the sheet do not go back to Monument.

The sheet holds three kinds of row: phases with start and end dates, milestones with a target date, and checklist items with a person and a due date.

1. Create an API key

Create a key in Settings under API Keys. The key sees what its creator sees. Projects that person cannot open do not appear. Send the key in the X-API-Key header on every request.

2. List your projects

Keep the id and name of each project you want in the sheet.

cURL
curl 'https://api.monument.so/api/v1/projects?limit=100' \
  -H 'X-API-Key: monument_your_api_key'

3. List each project's phases

Ask for the project's top-level tasks with parentTaskId. If your phases use their own task type, filter by taskTypeId instead. Read the type id from one phase in the first response. Each task has a startDate and an endDate.

cURL
# The project's top-level tasks (its phases, in most practices)
curl 'https://api.monument.so/api/v1/tasks?parentTaskId=PROJECT_ID&limit=100' \
  -H 'X-API-Key: monument_your_api_key'

# Or every task of one task type in the project
curl 'https://api.monument.so/api/v1/tasks?projectId=PROJECT_ID&taskTypeId=PHASE_TYPE_ID&limit=100' \
  -H 'X-API-Key: monument_your_api_key'
Response (trimmed)
{
  "data": [
    {
      "id": "TASK_ID",
      "name": "Concept design",
      "taskTypeId": "PHASE_TYPE_ID",
      "startDate": "2026-11-02",
      "endDate": "2026-12-18"
    }
  ],
  "pagination": { "total": 6, "limit": 100, "offset": 0 }
}

4. List each project's milestones

Each milestone has a name and a targetDate.

cURL
curl 'https://api.monument.so/api/v1/milestones?projectId=PROJECT_ID&limit=100' \
  -H 'X-API-Key: monument_your_api_key'

5. List each project's checklist items

Each item has its text, the person it is for (assigneeName), a dueDate, and whether it is done (isCompleted). The taskId tells you which phase or task the item belongs to. Use taskId=TASK_ID instead of projectId to read one task's checklist. This endpoint is read-only.

cURL
curl 'https://api.monument.so/api/v1/checklist-items?projectId=PROJECT_ID&limit=100' \
  -H 'X-API-Key: monument_your_api_key'
Response
{
  "data": [
    {
      "id": "CHECKLIST_ITEM_ID",
      "taskId": "TASK_ID",
      "projectId": "PROJECT_ID",
      "text": "Issue concept drawings to client",
      "isCompleted": false,
      "completedAt": null,
      "assigneeResourceId": "RESOURCE_ID",
      "assigneeName": "Sam Taylor",
      "dueDate": "2026-11-14",
      "displayOrder": 0
    }
  ],
  "pagination": { "total": 12, "limit": 100, "offset": 0 }
}
Lists return at most 100 rows per page. If pagination.total is larger than the rows you got, ask again with offset=100, then offset=200, and so on.

6. Get told when dates change

A webhook sends a message to your server when a date moves, so you do not have to read every project again. Subscribe to task.dates_changed and milestone.dates_changed. Only an admin key can create webhooks. The response has a signing secret that starts with whsec_. Store it.

cURL
curl -X POST 'https://api.monument.so/api/v1/webhooks' \
  -H 'X-API-Key: monument_your_admin_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Project dates to spreadsheet",
    "url": "https://example.com/hooks/monument",
    "events": ["task.dates_changed", "milestone.dates_changed"]
  }'

Monument sends one message per task or milestone whose dates changed. A drag that moves five phases sends five messages. Renaming or other edits send nothing. Each message gives the dates before and after the change:

task.dates_changed
{
  "event": "task.dates_changed",
  "timestamp": "2026-10-28T09:15:00.000Z",
  "data": {
    "id": "TASK_ID",
    "projectId": "PROJECT_ID",
    "rootTaskId": "PROJECT_ID",
    "name": "Concept design",
    "previous": { "startDate": "2026-11-02", "endDate": "2026-12-18" },
    "current": { "startDate": "2026-11-09", "endDate": "2026-12-23" }
  }
}
milestone.dates_changed
{
  "event": "milestone.dates_changed",
  "timestamp": "2026-10-28T09:15:00.000Z",
  "data": {
    "id": "MILESTONE_ID",
    "projectId": "PROJECT_ID",
    "rootTaskId": "PROJECT_ID",
    "name": "Planning submission",
    "previous": { "targetDate": "2027-01-15" },
    "current": { "targetDate": "2027-01-29" }
  }
}

7. Check the signature

Each message has an X-Monument-Signature header. It is sha256= followed by an HMAC-SHA256 of the raw body, made with your webhook secret. Make the same value on your side and compare. If they differ, ignore the message.

Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody: the request body exactly as received, before JSON.parse
function isValidSignature(rawBody, signatureHeader, secret) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
  const received = signatureHeader.replace('sha256=', '');

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  return a.length === b.length && timingSafeEqual(a, b);
}
Some script hosts cannot read request headers. Google Apps Script web apps are one of them. There you cannot check the signature, so do not trust the message itself. Use it only as a signal to read the task or milestone again from the API, with your key.

8. Map the data to sheet rows

Give the sheet one row per phase, milestone and checklist item. Keep the Monument id in a column, so an update finds its row again.

  • Phase: Type phase, Id from id, Project, Name, Start from startDate, End from endDate.
  • Milestone: Type milestone, Id, Project, Name, Due from targetDate.
  • Checklist item: Type checklist, Id, Project, Name from text, Due from dueDate, Person from assigneeName, Done from isCompleted.
  • On task.dates_changed: find the row with data.id and write data.current.startDate and data.current.endDate.
  • On milestone.dates_changed: find the row with data.id and write data.current.targetDate.

Checklist changes do not send webhooks. Read the checklist items again on a timer, for example every hour, to pick up new items, new people and new due dates.