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.
FieldTypeRequiredDescription
workspaceIduuidrequiredThe organization.
projectIduuidoptionalThe brand it belongs to.
titlestringrequiredIn words.
cadence"daily" | "weekly" | "once"requiredHow often it comes due.
weekday0–6optionalWeekly only: the due day, 0 = Monday.
dueDatedateoptionalOnce only: the date it’s due by.
startsOndateoptionalThe first day it counts. Nothing before it is judged; earlier steps are skipped.
timezoneIANA zoneoptionalThe zone its times are in.
itemsCommitmentItem[]requiredEach {label, dayOffset, at, proof}. proof: news_feed, issue_sent, youtube, manual.
firmbooleanoptionalA miss stays on top until done or excused.
positionintegeroptionalIts 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.