{"openapi":"3.1.0","info":{"title":"Moonto API","version":"1.0.0","description":"Your lists and rows over HTTP. Authenticate with a personal access token made in the app (Settings > Data > Access tokens): `Authorization: Bearer moonto_pat_...`. Read endpoints need the read scope, writes the write scope. Reading is free on every plan; an account without Premium can write 200 rows in total, through this API and the MCP endpoint together, to try it, then writing needs Moonto Premium (403 integration_writes_require_premium). Rate limits: 60 calls per minute per token and 2,000 per day per user, shared with the MCP endpoint. Rows are written like the app writes them; assistant requests leave a record in the app's history with Undo. Webhooks on row events are registered in the app (Settings > Data > Webhooks); their payloads and signature are described in the `webhooks` section below."},"servers":[{"url":"https://api.moonto.app"}],"paths":{"/api/v1/assist":{"post":{"operationId":"assist","summary":"Ask the assistant","description":"The app's assistant, in words: same body as /v1/assist. By default the answer comes once the request has finished, as JSON. Send `Accept: text/event-stream` to receive the events as they happen. The request leaves a record in the app's history, with Undo. The assistant needs Premium or an active trial: a free account gets 403 `assist_requires_premium`. Premium has the app's daily fair-use limit. A read-only question (`mode: global_question`) needs only the read scope.","security":[{"accessToken":["write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistBody"}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistResult"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token), or assist_requires_premium: the assistant needs Premium or an active trial."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"invalid_request: the request could not be read."},"504":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error."}}}},"/api/v1/lists":{"get":{"operationId":"lists","summary":"Lists tree","description":"The lists the token's user can see: personal ones, then each shared space with its lists. Use the ids with the row endpoints.","security":[{"accessToken":["read"]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListsTree"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token)."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."}}}},"/api/v1/lists/{listId}/rows":{"get":{"operationId":"rows","summary":"Rows of a list","description":"At most 50 rows, open ones unless isDone says otherwise. `truncated` and `total` tell when more matched; narrow with the date filters. A record kind (expenses, notes, people, cards, About me) is never ticked off: isDone is ignored there and every row is returned.","parameters":[{"name":"listId","in":"path","required":true,"description":"List id from GET /api/v1/lists.","schema":{"type":"string"}},{"name":"isDone","in":"query","required":false,"description":"true for done rows, false for open ones (the default). Ignored on a record kind such as expenses.","schema":{"type":"boolean"}},{"name":"limit","in":"query","required":false,"description":"1 to 50; default 50.","schema":{"type":"integer"}},{"name":"createdAfter","in":"query","required":false,"description":"Created at or after, epoch ms.","schema":{"type":"integer","format":"int64"}},{"name":"createdBefore","in":"query","required":false,"description":"Created at or before, epoch ms.","schema":{"type":"integer","format":"int64"}},{"name":"dueAfter","in":"query","required":false,"description":"Due at or after, epoch ms.","schema":{"type":"integer","format":"int64"}},{"name":"dueBefore","in":"query","required":false,"description":"Due at or before, epoch ms.","schema":{"type":"integer","format":"int64"}}],"security":[{"accessToken":["read"]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RowsPage"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token)."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"not_found: no such list or row for this user."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rejected or missing_required_fields: the tool refused the input; nothing was written."}}},"post":{"operationId":"createRows","summary":"Add rows to a list","description":"Up to 50 rows, validated like the app validates them: nothing is written if one row is refused. These are ordinary rows; there is no Undo record. To act in words with Undo in the app, use POST /api/v1/assist.","parameters":[{"name":"listId","in":"path","required":true,"description":"List id from GET /api/v1/lists.","schema":{"type":"string"}}],"security":[{"accessToken":["write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRowsBody"}}},"required":true},"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRowsResult"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token), or integration_writes_require_premium: an account without Premium has written its 200 rows (the body says how many are left and links to the upgrade page)."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"not_found: no such list or row for this user."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rejected or missing_required_fields: the tool refused the input; nothing was written."}}}},"/api/v1/openapi.json":{"get":{"operationId":"openapi","summary":"This document","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApiDocument"}}}}}}},"/api/v1/rows/{rowId}":{"patch":{"operationId":"updateRow","summary":"Update a row","description":"Changes the given fields of one row; the others stay.","parameters":[{"name":"rowId","in":"path","required":true,"description":"Row id.","schema":{"type":"string"}}],"security":[{"accessToken":["write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRowBody"}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRowResult"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token), or integration_writes_require_premium: an account without Premium has written its 200 rows (the body says how many are left and links to the upgrade page)."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"not_found: no such list or row for this user."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rejected or missing_required_fields: the tool refused the input; nothing was written."}}},"delete":{"operationId":"deleteRow","summary":"Delete a row","description":"Rows holding an uploaded photo or file are refused (422): delete those in the app so their files stay recoverable. The row's reminder, if the token's user has one, goes with it.","parameters":[{"name":"rowId","in":"path","required":true,"description":"Row id.","schema":{"type":"string"}}],"security":[{"accessToken":["write"]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRowResult"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token), or integration_writes_require_premium: an account without Premium has written its 200 rows (the body says how many are left and links to the upgrade page)."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"not_found: no such list or row for this user."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rejected or missing_required_fields: the tool refused the input; nothing was written."}}}},"/api/v1/search":{"get":{"operationId":"search","summary":"Search rows","description":"Meaning-based search across the user's rows, best match first.","parameters":[{"name":"q","in":"query","required":true,"description":"What to look for, in words.","schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"description":"Restrict to these kind keys; repeat the parameter.","schema":{"type":"array","items":{"type":"string"}}},{"name":"spaceId","in":"query","required":false,"description":"Restrict to one shared space.","schema":{"type":"string"}}],"security":[{"accessToken":["read"]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResult"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing, invalid, revoked or expired access token."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"insufficient_scope (a write needs a write token)."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rate_limited; see Retry-After."},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"rejected or missing_required_fields: the tool refused the input; nothing was written."}}}}},"components":{"securitySchemes":{"accessToken":{"description":"A personal access token, moonto_pat_...","scheme":"bearer","type":"http"}},"schemas":{"Error":{"type":"object","description":"A refused call.","properties":{"code":{"type":"string","description":"Machine code, e.g. insufficient_scope, rate_limited, not_found, rejected, missing_required_fields, invalid_request."},"error":{"type":"string","description":"A sentence for a human."},"scope":{"type":"string","description":"For rate_limited: minute or day."},"retryAfterSeconds":{"type":"integer"},"missingRequiredFields":{"type":"array","items":{"type":"object"}}},"required":["code"]},"ListEntry":{"type":"object","description":"A list.","properties":{"id":{"type":"string","description":"List id."},"name":{"type":"string","description":"Name."},"kind":{"type":"string","description":"Kind key, e.g. shopping, tasks, expenses, notes."}},"required":["id","name","kind"]},"ListsTree":{"type":"object","description":"The lists the token's user can see, personal first.","properties":{"personal":{"type":"array","items":{"$ref":"#/components/schemas/ListEntry"}},"spaces":{"type":"array","items":{"type":"object","description":"A shared space.","properties":{"id":{"type":"string","description":"Space id."},"name":{"type":"string","description":"Space name; null when the user is not a member of the space doc."},"lists":{"type":"array","items":{"$ref":"#/components/schemas/ListEntry"}}},"required":["id","lists"]}}},"required":["personal","spaces"]},"Row":{"type":"object","description":"A row as the assistant sees it: id, title, the kind's fields, isDone and dates. Same shape as the listRows tool.","properties":{"currency":{"type":"string","description":"ISO 4217 code of the row's money (its money fields, a shopping row's price), e.g. USD, on kinds that have money. Absent means the user's own currency. Write it the same way on create and update; null clears it."}},"additionalProperties":true},"RowsPage":{"type":"object","description":"Rows of a list, at most 50.","properties":{"success":{"type":"boolean"},"resultCount":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/Row"}},"truncated":{"type":"boolean","description":"More rows matched than returned."},"total":{"type":"integer","description":"Rows that matched, when truncated."}},"required":["results"]},"CreateRowsBody":{"type":"object","description":"Rows to add to the list.","properties":{"rows":{"maxItems":50,"type":"array","items":{"description":"One row: title and the kind's fields, as in the addRows tool. Required fields of the kind must be present. A reminderDate (ISO 8601 local date-time or epoch milliseconds) creates the row's reminder for the token's user, which notifies them like one set in the app.","type":"object","additionalProperties":true},"minItems":1}},"required":["rows"]},"CreateRowsResult":{"type":"object","description":"What was added.","properties":{"success":{"type":"boolean"},"listId":{"type":"string","description":"List id."},"listName":{"type":"string","description":"List name."},"kind":{"type":"string","description":"Kind key."},"rowIds":{"type":"array","items":{"type":"string","description":"New row id, or in a shopping list the id of the row the item was already on."}},"rows":{"type":"array","items":{"type":"object","description":"A new row, or in a shopping list the row the item was already on.","properties":{"rowId":{"type":"string","description":"Id."},"title":{"type":"string","description":"Title."},"outcome":{"description":"Absent when the row is new. A shopping list never holds the same item twice: merged = its quantity was added to the row, alreadyThere = the row stays as it was, reopened = the checked-off row is open again.","type":"string","enum":["merged","alreadyThere","reopened"]},"quantity":{"type":"string","description":"The row's quantity after a merge, e.g. \"3 L\"."}},"required":["rowId"]}},"acknowledgment":{"type":"string","description":"A sentence describing the write."}},"required":["success","rowIds"]},"UpdateRowBody":{"type":"object","description":"The fields to change; the others stay.","properties":{"fields":{"description":"Declared fields of the row's kind, as in the updateRow tool. reminderDate creates or moves the row's reminder for the token's user; null removes it.","type":"object","additionalProperties":true}},"required":["fields"]},"UpdateRowResult":{"type":"object","description":"What changed.","properties":{"success":{"type":"boolean"},"rowId":{"type":"string","description":"Row id."},"listId":{"type":"string","description":"List id."},"listName":{"type":"string","description":"List name."},"title":{"type":"string","description":"Row title."},"updatedFields":{"type":"array","items":{"type":"string","description":"Field key."}},"acknowledgment":{"type":"string","description":"A sentence."}},"required":["success","rowId"]},"DeleteRowResult":{"type":"object","description":"What was deleted.","properties":{"success":{"type":"boolean"},"rowId":{"type":"string","description":"Row id."},"listId":{"type":"string","description":"List id."},"listName":{"type":"string","description":"List name."},"title":{"type":"string","description":"Row title."},"acknowledgment":{"type":"string","description":"A sentence."}},"required":["success","rowId"]},"SearchResult":{"type":"object","description":"Rows matching the query, best first.","properties":{"success":{"type":"boolean"},"query":{"type":"string","description":"The query."},"resultCount":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/Row"}},"truncated":{"type":"boolean"}},"required":["results"]},"AssistBody":{"type":"object","description":"The same body as the app's /v1/assist.","properties":{"text":{"type":"string","description":"What to do or ask, in the user's words."},"listId":{"type":"string","description":"The list the request is about (a pointer, re-checked server-side)."},"kindKey":{"type":"string","description":"That list's kind key (a hint)."},"locale":{"type":"string","description":"Locale such as fr or fr_FR."},"timezone":{"type":"string","description":"IANA zone id."},"utcOffsetMinutes":{"type":"integer"},"mode":{"type":"string","description":"Omit for a request that may write; global_question for a read-only question (needs only the read scope)."}},"required":["text"]},"AssistResult":{"type":"object","description":"The outcome of one assistant request, once finished. With Accept: text/event-stream the same facts arrive as server-sent events instead (transcript, step, rowCreated, rowUpdated, rowDeleted, text, question, done, error).","properties":{"status":{"type":"string","enum":["done","question","error"]},"requestId":{"type":"string","description":"The CPAIRequest id; the record shows in the app's history and can be undone there."},"summary":{"type":"string","description":"What was done, in a sentence."},"text":{"type":"string","description":"The assistant's reply text."},"question":{"type":"object","description":"Set when the assistant needs a value from the user.","properties":{"text":{"type":"string","description":"The question."},"missingFields":{"type":"array","items":{"type":"object"}}}},"changes":{"type":"object","description":"Rows touched.","properties":{"created":{"type":"array","items":{"type":"object","description":"A row.","properties":{"listId":{"type":"string","description":"List id."},"itemId":{"type":"string","description":"Row id."},"title":{"type":"string","description":"Title."}}}},"updated":{"type":"array","items":{"type":"object","description":"A row.","properties":{"listId":{"type":"string","description":"List id."},"itemId":{"type":"string","description":"Row id."},"title":{"type":"string","description":"Title."}}}},"deleted":{"type":"array","items":{"type":"object","description":"A row.","properties":{"listId":{"type":"string","description":"List id."},"itemId":{"type":"string","description":"Row id."},"title":{"type":"string","description":"Title."}}}}}},"undoActions":{"type":"array","items":{"type":"object"}},"errorCode":{"type":"string","description":"Set when status is error."}},"required":["status"]},"OpenApiDocument":{"type":"object","description":"This document.","additionalProperties":true}}},"webhooks":{"rowEvent":{"post":{"summary":"A row event","description":"Moonto POSTs this to your URL. Headers: `X-Moonto-Event` (the type), `X-Moonto-Delivery` (the delivery id), `X-Moonto-Timestamp` (Unix seconds, fresh on every attempt) and `X-Moonto-Signature: v1=<hex>`, where the hex is HMAC-SHA256 with your webhook secret over the string `<timestamp>.<raw body>`. Compare in constant time and reject a timestamp more than 5 minutes old. Answer 2xx within 10 seconds; anything else (redirects included) is retried after 1 min, 5 min, 30 min, 2 h and 6 h (six attempts), then dropped. 410 Gone disables the webhook; so do five dropped deliveries in a row.","requestBody":{"content":{"application/json":{"schema":{"type":"object","description":"One delivery. The body is JSON; verify it before trusting it.","required":["id","type","createdAt","data"],"properties":{"data":{"type":"object","properties":{"row":{"type":"object","description":"The row as stored (for rowDeleted: as it was just before)."},"listName":{"type":"string"},"listId":{"type":"string"},"rowId":{"type":"string"},"kind":{"type":"string"},"spaceId":{"type":"string"},"truncated":{"type":"boolean","description":"The row was over 200 KB; only id, title and listId are included."},"changedFields":{"type":"array","items":{"type":"string"}}}},"createdAt":{"type":"integer","description":"Epoch ms of the event."},"id":{"type":"string","description":"Delivery id; the same on every retry, use it to de-duplicate."},"type":{"type":"string","enum":["rowCreated","rowUpdated","rowDeleted"]}}}}},"required":true},"responses":{"2XX":{"description":"Received"}}}}}}