Reference · Commitments API
Commitments API
What must happen, with proof. Members read; owners and admins create and change. Changes carry the commitment’s etag and return 409 with the current one when it’s stale. The model: Commitments.
Commitments
GET/v1/commitments?workspaceId=The active commitments.
POST/v1/commitmentsCreate one. Owners and admins.
PATCH/v1/commitments/{commitmentId}Change one, with If-Match. startsOn: null clears a fresh start.
DELETE/v1/commitments/{commitmentId}Archive it. Its record stays.
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId | uuid | required | The organization. |
projectId | uuid | optional | The brand it belongs to. |
title | string | required | In words. |
cadence | "daily" | "weekly" | "once" | required | How often it comes due. |
weekday | 0–6 | optional | Weekly only: the due day, 0 = Monday. |
dueDate | date | optional | Once only: the date it’s due by. |
startsOn | date | optional | The first day it counts. Nothing before it is judged; earlier steps are skipped. |
timezone | IANA zone | optional | The zone its times are in. |
items | CommitmentItem[] | required | Each {label, dayOffset, at, proof}. proof: news_feed, issue_sent, youtube, manual. |
firm | boolean | optional | A miss stays on top until done or excused. |
position | integer | optional | Its order on the page. |
a daily commitment, ticked by handjson
POST /v1/commitments
{
"workspaceId": "…",
"projectId": "…",
"title": "One Instagram post for the newsletter",
"cadence": "daily",
"timezone": "America/New_York",
"startsOn": "2026-11-03",
"items": [
{ "label": "Posted", "dayOffset": 0, "at": "12:00", "proof": "manual" }
]
}Today
GET/v1/commitments/today?workspaceId=Each commitment’s current day judged now, with its record, history, streak and best; the unresolved misses; the score; the month’s rest days.
today, abridgedjsonc
GET /v1/commitments/today?workspaceId=…
{
"items": [
{
"commitment": { "id": "…", "title": "Three news posts", "cadence": "daily", "etag": "\"4\"", … },
"occurrence": {
"dueOn": "2026-11-03",
"status": "open",
"summary": "1 of 3",
"items": [
{ "label": "News post 1", "proof": "news_feed", "dueAt": "2026-11-03T14:00:00Z",
"state": "done", "onTime": true, "doneAt": "…", "evidence": { "title": "…", "url": "…" } },
{ "label": "News post 2", "proof": "news_feed", "dueAt": "2026-11-03T14:00:00Z",
"state": "open", "onTime": false }
]
},
"record": [ { "dueOn": "2026-11-02", "status": "met", "onTime": true } ],
"history": [ { "dueOn": "2026-11-02", "status": "met", "onTime": true, "rested": false } ],
"streak": 6,
"best": 12
}
],
"unresolved": [],
"score": { "dailyDone": 1, "dailyDue": 3, "todayPoints": 10, "level": 3, … },
"rests": { "perMonth": 2, "used": 0, "left": 2 }
}An item’s state is done, late, open, unknown (Esy couldn’t read the source just now) or skipped (due before startsOn, so not counted). unresolved holds days in the last week that were missed and not excused.
A day: done, excused, rested
POST/v1/commitments/{commitmentId}/occurrences/{dueOn}/items/{index}:doneMark a step done by hand. Not news: the feed proves those.
POST/v1/commitments/{commitmentId}/occurrences/{dueOn}/items/{index}:undoTake a by-hand mark back.
POST/v1/commitments/{commitmentId}/occurrences/{dueOn}:excuseExcuse the day (or the week) with {reason}. It stays on the record.
POST/v1/commitments/{commitmentId}/occurrences/{dueOn}:restTake today off a daily item on purpose. Two a month; holds the run.
POST/v1/commitments/{commitmentId}/occurrences/{dueOn}:unrestGive the rest day back.
index is the item’s position in the commitment’s items, not in the day’s sorted list.
Esy counts; you read
Runs, points, levels, badges and perfect days come back computed. Don’t recompute them in a client: read
score, streak and history from today.