{"openapi":"3.1.0","info":{"title":"miracle.fyi v1","version":"1.1.0","description":"Agent-facing REST API for miracle.fyi — todos, pacts, goals, and the 3·6·9 journal. Authenticated via a per-user bearer token (Settings -> Connect AI tools). Errors share a stable { error, code } shape so agents can pattern-match on codes. Money-staked pacts accept /check but reject /uncheck (staked_write_forbidden).","contact":{"name":"miracle.fyi","url":"https://miracle.fyi"}},"servers":[{"url":"https://miracle.fyi/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Per-user token from Settings -> Connect AI tools. Same secret as the inbox webhook URL."}},"schemas":{"Todo":{"type":"object","required":["id","text","today","url"],"properties":{"id":{"type":"string","example":"todo_a1b2c3d4e5f6a7b8"},"text":{"type":"string","maxLength":240},"notes":{"type":"string","maxLength":10000},"today":{"type":"boolean"},"completed_at":{"type":["string","null"],"format":"date-time"},"archived_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"},"url":{"type":"string","format":"uri"}}},"TodoResponse":{"type":"object","required":["todo"],"properties":{"todo":{"$ref":"#/components/schemas/Todo"}}},"TodoList":{"type":"object","required":["todos","count","has_more"],"properties":{"todos":{"type":"array","items":{"$ref":"#/components/schemas/Todo"}},"count":{"type":"integer","minimum":0},"has_more":{"type":"boolean"}}},"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"Human-readable message."},"code":{"type":"string","description":"Machine-readable. Stable across versions.","enum":["bad_token","not_found","must_archive_first","text_required","bad_status","bad_segment","date_locked","staked_write_forbidden","db_error","db_unavailable","rate_limited"]}}},"TodoCreate":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":240},"today":{"type":"boolean","default":false},"notes":{"type":"string","maxLength":10000},"completed_at":{"type":"string","format":"date-time"}}},"TodoPatch":{"type":"object","properties":{"text":{"type":"string","maxLength":240},"today":{"type":"boolean"},"notes":{"type":["string","null"],"maxLength":10000},"completed_at":{"type":["string","null"],"format":"date-time"},"archived_at":{"type":["string","null"],"format":"date-time"}}},"Me":{"type":"object","required":["id"],"description":"The authenticated caller's own identity.","properties":{"id":{"type":"string","example":"user_01H...","description":"Stable user id."},"email":{"type":["string","null"],"format":"email"},"name":{"type":["string","null"]}}},"Pact":{"type":"object","required":["id","title","type","mode","stake_cents","url"],"description":"A daily commitment. `mode` is simple (yes/no), numeric (segmented), journal (the 3·6·9 ritual, auto-verified), or count. A non-zero `stake_cents` (or `staked: true`) means money is on the line — such pacts accept /check but reject /uncheck (staked_write_forbidden).","properties":{"id":{"type":"string","example":"pact_a1b2c3d4e5f6a7b8"},"title":{"type":"string"},"type":{"type":"string","enum":["individual","group"]},"mode":{"type":"string","enum":["simple","numeric","journal","count"]},"stake_cents":{"type":"integer","minimum":0,"description":"Live money stake in cents (0 = free)."},"staked":{"type":"boolean","description":"Convenience flag: stake_cents > 0."},"today_status":{"type":["string","null"],"enum":["done","missed","rest",null],"description":"Status for the caller's local-tz today (null = not yet acted on)."},"target":{"type":["number","null"],"description":"Numeric-mode total target (else null)."},"unit":{"type":["string","null"]},"segments":{"type":["integer","null"],"description":"Numeric-mode segment count (else null)."},"description":{"type":["string","null"]},"owed_cents":{"type":["integer","null"],"description":"Uncollected owed cents from missed days."},"archived":{"type":"boolean"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"},"url":{"type":"string","format":"uri"}}},"PactResponse":{"type":"object","required":["pact"],"properties":{"pact":{"$ref":"#/components/schemas/Pact"}}},"PactList":{"type":"object","required":["pacts","count"],"properties":{"pacts":{"type":"array","items":{"$ref":"#/components/schemas/Pact"}},"count":{"type":"integer","minimum":0}}},"PactCheck":{"type":"object","description":"Body for /check and /uncheck. Both fields optional.","properties":{"date":{"type":"string","format":"date","description":"YYYY-MM-DD. Defaults to the caller's local-tz today."},"segment_index":{"type":"integer","minimum":0,"maximum":23,"description":"Numeric-mode segment (0-based). Omit for simple/journal pacts."}}},"PactCheckResult":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"date":{"type":"string","format":"date"},"segment_index":{"type":"integer"},"pact":{"$ref":"#/components/schemas/Pact"}}},"Goal":{"type":"object","required":["level","text","show_on_home"],"description":"One level of the five-level goal grid.","properties":{"level":{"type":"string","enum":["bhag","month","week","day","now"]},"text":{"type":"string","description":"The goal as written (bhag is the lifetime/'year' goal)."},"show_on_home":{"type":"boolean","description":"Whether this level is pinned to the Home list."},"done":{"type":"boolean","description":"Marked done for the caller's local-tz today."},"type":{"type":"string","enum":["qualitative","quantitative"]},"verb":{"type":"string"},"number":{"type":["number","null"]},"unit":{"type":"string"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"GoalsResponse":{"type":"object","required":["goals"],"properties":{"goals":{"type":"object","description":"Keyed by level (bhag/month/week/day/now).","additionalProperties":{"$ref":"#/components/schemas/Goal"}}}},"GoalResponse":{"type":"object","required":["goal"],"properties":{"goal":{"$ref":"#/components/schemas/Goal"}}},"GoalPatch":{"type":"object","description":"Partial update of one goal level. Both fields optional.","properties":{"text":{"type":"string","maxLength":2000},"show_on_home":{"type":"boolean"}}},"JournalSession":{"type":"object","required":["target","written","done"],"properties":{"target":{"type":"integer","description":"Lines expected (3 morning / 6 noon / 9 evening)."},"written":{"type":"integer","description":"Non-empty lines written so far."},"done":{"type":"boolean"}}},"JournalDay":{"type":"object","required":["date","sessions"],"properties":{"date":{"type":"string","format":"date"},"sessions":{"type":"object","required":["morning","noon","evening"],"properties":{"morning":{"$ref":"#/components/schemas/JournalSession"},"noon":{"$ref":"#/components/schemas/JournalSession"},"evening":{"$ref":"#/components/schemas/JournalSession"}}}}}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Optional client-generated key. Same key + same user = same todo (returns 200 with the existing row instead of creating a duplicate)."},"TodoId":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Todo id returned from a previous create/list call."},"PactId":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Pact id returned from a previous list/fetch call."},"GoalLevel":{"name":"level","in":"path","required":true,"schema":{"type":"string","enum":["bhag","month","week","day","now"]},"description":"Goal grid level. 'bhag' is the lifetime/'year' goal."}},"responses":{"Unauthorized":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found (or not yours).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The mutation is not allowed. For pacts: code='staked_write_forbidden' means the pact has a money stake and the API may not erase a completion (use the app).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"The target day is locked (code='date_locked') — past money-staked days outside the 2-day edit window, or a day with a settled miss, are immutable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Rate limited. The Retry-After header (seconds) and RateLimit-* headers tell you when to retry. Body code is 'rate_limited'.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServerError":{"description":"Server-side error. 500 = unexpected failure (e.g. db_error); 503 = a dependency is unconfigured/unavailable (e.g. db_unavailable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/todos":{"get":{"summary":"List todos","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","done","archived","all"],"default":"active"}},{"name":"today","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}},"post":{"summary":"Create todo","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoCreate"}}}},"responses":{"200":{"description":"Replay — existing todo for that Idempotency-Key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/todos/{id}":{"parameters":[{"$ref":"#/components/parameters/TodoId"}],"get":{"summary":"Fetch one","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}},"patch":{"summary":"Update fields","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoPatch"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}},"delete":{"summary":"Hard delete (archived only)","responses":{"204":{"description":"Deleted."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/todos/{id}/complete":{"parameters":[{"$ref":"#/components/parameters/TodoId"}],"post":{"summary":"Mark complete","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/todos/{id}/uncomplete":{"parameters":[{"$ref":"#/components/parameters/TodoId"}],"post":{"summary":"Undo complete","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/todos/{id}/archive":{"parameters":[{"$ref":"#/components/parameters/TodoId"}],"post":{"summary":"Soft-delete (archive)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/todos/{id}/unarchive":{"parameters":[{"$ref":"#/components/parameters/TodoId"}],"post":{"summary":"Restore from archive","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/me":{"get":{"summary":"Who am I","description":"Returns the identity tied to the bearer token in use.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/pacts":{"get":{"summary":"List pacts","description":"The caller's own active pacts (owned or actively joined).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PactList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/pacts/{id}":{"parameters":[{"$ref":"#/components/parameters/PactId"}],"get":{"summary":"Fetch one pact","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PactResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/pacts/{id}/check":{"parameters":[{"$ref":"#/components/parameters/PactId"}],"post":{"summary":"Mark kept for a day","description":"Records the pact as KEPT for the given day. Allowed even on money-staked pacts — recording a completion can only prevent a charge.","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PactCheck"}}}},"responses":{"200":{"description":"Checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PactCheckResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/pacts/{id}/uncheck":{"parameters":[{"$ref":"#/components/parameters/PactId"}],"post":{"summary":"Remove a check","description":"Removes a completion for the given day. FORBIDDEN on money-staked pacts (403 staked_write_forbidden) — erasing a completion is a stake-dodge. Allowed on free pacts.","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PactCheck"}}}},"responses":{"200":{"description":"Unchecked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PactCheckResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/goals":{"get":{"summary":"List goals","description":"All five levels of the goal grid, keyed by level.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalsResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/goals/{level}":{"parameters":[{"$ref":"#/components/parameters/GoalLevel"}],"patch":{"summary":"Update one goal level","description":"Updates text and/or show_on_home for the level. Both fields optional.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalPatch"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}},"/journal":{"get":{"summary":"Read a journal day","description":"Read-only view of the three 3·6·9 sessions for a day. Journal write for agents is deferred until the journal is relational.","parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD. Defaults to the caller's local-tz today."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalDay"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/ServerError"},"503":{"$ref":"#/components/responses/ServerError"}}}}}}