Day reviews
A day review records how a past day went, compared to the plan. When you confirm a review, Reassign freezes the adherence scores of that day.
The machine-readable spec of all public endpoints: openapi.json.
How a review works
- Mark each event of the past day with the
reflectop ofPOST /events. Refer to Events. - Confirm the day with
POST /reviews/{date}and"action": "confirm". The server calculates the scores and freezes them. - Read the review in
GET /schedule. A confirmed day has areviewblock indays[].review. Refer to Scheduling. - To start again, discard the review with
"action": "discard".
There is no separate route to read a review. days[].review is the only public read. Its reviewedAt field shows that the day is confirmed.
You can confirm a day again. The server then calculates the scores again and replaces the snapshot. A day has one review or no review.
The reflect op
Send one reflect op for each event in the ops of POST /events.
The event's day must be before today in the account's time zone. An event on today or a future day fails with validation, even if today's event has already ended. MCP write_events follows the same rule.
status | Meaning | Actual times |
|---|---|---|
kept | The event occurred as planned. | Do not send them. |
skipped | The event did not occur. | Do not send them. |
changed | The event occurred, but in a different way. | Optional: actualStart and actualEnd together. |
added | The event was not in the plan, but it occurred. | Optional: actualStart and actualEnd together. They become the time of the event. |
actualStartandactualEndare local datetimes"YYYY-MM-DDTHH:MM".actualEndmust be afteractualStart.- For one occurrence of a series, use the occurrence id
seriesId@YYYY-MM-DDfrom a read. A bare series id is an error. - The
reflectop also works on areadOnlyevent. - A read shows the mark in the
reflectfield of the event.
Scores of each event
| Mark | Score |
|---|---|
kept | 1 |
| No mark | 1 (the same as kept) |
skipped | 0 |
changed with no actual times, or with the planned times | 0.5 |
changed with actual times that differ from the plan | The part of the planned time that the actual time covers, from 0 to 1 |
added | Not in the score |
Events of the kind reference are not in the scores.
Adherence scores
The review block has reviewedAt (an ISO 8601 instant) and adherence. The server rounds the scores to 2 decimal places.
| Field | Meaning |
|---|---|
adherence.event | The mean score of the planned events, weighted by duration. From 0 to 1, or null. |
adherence.layer | The part of the planned time that has an area, where the actual time has the same area. From 0 to 1, or null. |
adherence.plannedMinutes | The total duration of the planned events. The added events are not in this total. |
adherence.unplannedMinutes | The total duration of the added events. |
adherence.byArea | One item for each area: areaId (null for the events with no area), adherence, and plannedMinutes. |
adherence.byActivityType | One item for each activity type: activityTypeId, adherence, and plannedMinutes. |
- When the day has no planned time (
plannedMinutesis 0),eventandlayerarenull.layeris alsonullwhen no planned event has an area. - A snapshot that Reassign froze before this rule can have 1 in these cases. Treat a score as
nullwhenplannedMinutesis 0. Treatlayerasnullwhen nobyAreaitem with an area has planned time. - A review that Reassign confirmed before it started to freeze the scores has no
adherence.
Scopes
| Route | Scope |
|---|---|
POST /reviews/{date} | events:write |
GET /schedule (to read days[].review) | events:read or events:write |
All routes need a trial or Pro plan. Refer to Authentication. All dates are "YYYY-MM-DD" in the time zone of the account. For the error format, refer to Errors.
Confirm or discard a review
Confirm or discard the review of one day. Send action: "confirm" or "discard". Scope: events:write.
- A
datein the path that is not a real date returns 422 with the messageThe path `date` must be a real ISO YYYY-MM-DD. - Both actions return an
undoTokenand itsexpiresAt, except adiscardthat changes nothing. Send the token toPOST /actions/undoin 30 minutes or less to reverse the action. Refer to Scheduling. - If the server cannot write the undo record, the response has no
undoToken. The change stays.
Confirm
confirm calculates the scores from all events of the day, except reference events, and freezes them. An event with no mark counts as kept.
- The day must be before today, in the time zone of the account.
- Today or a future day returns 422
validationwith the messageYou can only reflect on a past day — pick a date before today. - There is no limit on how far back the day can be.
- A day where no event has a mark also confirms. This is the same as "Looks right" in the app. The response then has a
warning:No event on this day has a reflection mark. The score counts each unmarked event as kept.A day with no events gets thewarningThis day has no events, so the review has no score. - The response is
date, thereviewblock fields (reviewedAtandadherence), thewarningwhen it applies, and the undo receipt. - An undo of a confirm puts back the review that the day had before, or removes the review.
Discard
discard resets the full reflection of the day:
- It deletes the review.
- It deletes the events that have the mark
added. Their ids are indeletedIds. - It removes the marks and the actual times from the other marked events. These events are in
updated, with their spans intimezone.
Other rules:
discardhas no date check. It also works on today or a future day.- When the day has no review and no marked events,
discardsucceeds and changes nothing. It returns{ date, timezone, updated: [], deletedIds: [], noop: true }with noundoToken.
Path parameters
- Name
date- Type
- string, required
- Description
The day, ISO "YYYY-MM-DD".
Request body
- Name
action- Type
- "confirm" | "discard", required
- Description
"confirm" freezes the adherence snapshot of the day; "discard" resets the whole reflection.
Response fields
One form: With
reviewedAt- Name
date- Type
- string, required
- Description
- Name
reviewedAt- Type
- string, required
- Description
- Name
adherence- Type
- object
- Description
6 fields
- Name
event- Type
- number | null, required
- Description
- Name
layer- Type
- number | null, required
- Description
- Name
plannedMinutes- Type
- integer, required
- Description
- Name
unplannedMinutes- Type
- integer, required
- Description
- Name
byArea- Type
- object[], required
- Description
3 fields
- Name
areaId- Type
- string | null, required
- Description
- Name
adherence- Type
- number, required
- Description
- Name
plannedMinutes- Type
- integer, required
- Description
- Name
byActivityType- Type
- object[], required
- Description
3 fields
- Name
activityTypeId- Type
- string | null, required
- Description
- Name
adherence- Type
- number, required
- Description
- Name
plannedMinutes- Type
- integer, required
- Description
- Name
warning- Type
- string
- Description
- Name
undoToken- Type
- string
- Description
- Name
expiresAt- Type
- string
- Description
One form: With
timezone,updated,deletedIds- Name
date- Type
- string, required
- Description
- Name
timezone- Type
- string, required
- Description
- Name
updated- Type
- object[], required
- Description
26 fields
- Name
start- Type
- string, required
- Description
- Name
end- Type
- string, required
- Description
- Name
id- Type
- string, required
- Description
- Name
seriesId- Type
- string
- Description
- Name
originalDate- Type
- string
- Description
- Name
kind- Type
- "blocking" | "non_blocking" | "reference", required
- Description
- Name
name- Type
- string, required
- Description
- Name
notes- Type
- string
- Description
- Name
source- Type
- "google" | "google_tasks" | "linear" | "microsoft" | "microsoft_todo" | "ticktick" | "todoist" | "reassign", required
- Description
- Name
sourceUrl- Type
- string
- Description
- Name
meeting- Type
- object
- Description
2 fields
- Name
url- Type
- string, required
- Description
- Name
label- Type
- string, required
- Description
- Name
location- Type
- object
- Description
2 fields
- Name
text- Type
- string, required
- Description
- Name
url- Type
- string
- Description
- Name
recurrence- Type
- string
- Description
- Name
warning- Type
- string
- Description
- Name
areaId- Type
- string | null, required
- Description
- Name
activityTypeId- Type
- string | null, required
- Description
- Name
projectId- Type
- string | null, required
- Description
- Name
priority- Type
- integer, required
- Description
- Name
calendarId- Type
- string | null
- Description
- Name
mirrorCalendarIds- Type
- string[]
- Description
- Name
mirrorStyles- Type
- map of "full" | "private" | "busy"
- Description
- Name
readOnly- Type
- true
- Description
- Name
focusIntervals- Type
- object
- Description
2 fields
- Name
focusMin- Type
- integer, required
- Description
- Name
breakMin- Type
- integer, required
- Description
- Name
completedFocusIntervals- Type
- integer
- Description
- Name
checklist- Type
- object
- Description
1 fields
- Name
items- Type
- object[], required
- Description
3 fields
- Name
id- Type
- string, required
- Description
- Name
text- Type
- string, required
- Description
- Name
done- Type
- boolean, required
- Description
- Name
reflect- Type
- object
- Description
2 forms
One form: Without
actualStart,actualEnd- Name
status- Type
- "kept" | "skipped" | "changed" | "added", required
- Description
One form: With
actualStart,actualEnd- Name
status- Type
- "changed", required
- Description
- Name
actualStart- Type
- string, required
- Description
- Name
actualEnd- Type
- string, required
- Description
- Name
deletedIds- Type
- string[], required
- Description
- Name
noop- Type
- true
- Description
- Name
undoToken- Type
- string
- Description
- Name
expiresAt- Type
- string
- Description
Responses
| Status | Codes | Meaning |
|---|---|---|
200 | — | Success. |
401 | unauthorized | No Bearer token and no session cookie, or the token is invalid, expired, or revoked. The response carries a WWW-Authenticate challenge that points at the protected-resource metadata. |
403 | permissionscope | A token without events:write gets scope "This token is not authorized to modify events (missing events:write scope)." An account without Reassign Pro access gets permission "Reassign needs an active subscription." The 7-day free trial includes Reassign Pro. A cookie session from another site gets permission "Cross-origin request." |
422 | validation | The request body is not valid. The message names the first bad field by its path. A repeated or unknown query parameter is also a 422. |
429 | rate_limited | Too many requests. The edge firewall limits each IP to 2000 requests per minute across /api/v1; that 429 comes from the edge and can have a body that is not this JSON and no Retry-After header. When an edge 429 has no Retry-After, retry after 60 seconds. The app also limits each Bearer credential to 600 requests per minute, and some routes limit each user. These limits return this body. This app 429 always has Retry-After: retry after that number of seconds. |
503 | internal | A fault on our side. The request is safe to retry. |
Request
curl -X POST "https://reassign.app/api/v1/reviews/2026-08-14" \
-H "Authorization: Bearer rsk_live_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "confirm"
}'Response
{
"date": "2026-08-12",
"reviewedAt": "2026-08-13T07:12:00.000Z",
"adherence": {
"event": 0.8,
"layer": 0.74,
"plannedMinutes": 450,
"unplannedMinutes": 60,
"byArea": [
{
"areaId": "3c9d2a41-6b7e-4f80-9a1b-2c3d4e5f6071",
"adherence": 0.9,
"plannedMinutes": 300
}
],
"byActivityType": [
{
"activityTypeId": null,
"adherence": 0.8,
"plannedMinutes": 450
}
]
},
"undoToken": "00000000-0000-4000-8000-000000000001",
"expiresAt": "2026-08-13T09:12:00.000Z"
}