{"ok":true,"name":"QuestLog API","version":"v1","versioning":"Use the /api/v1/ routes listed below. If a v2 ever exists, v1 keeps answering exactly as it does today. The older unversioned form, /api/..., is the same route and stays open for anything already written against it. Any other version, such as /api/v2/, answers 404 until it is real.","auth":"Authorization: Bearer questlog_...  Accounts are not self serve: ask for one and a key is issued to you. The key is shown once and cannot be recovered. Once you have one, POST /api/v1/keys adds further keys to your own account, one per device or script.","shape":"Reads answer in camelCase. Writes accept camelCase or snake_case for the same field. A whole account reads as one document from GET /api/v1/projects. The `version` number inside that document is the format of the document itself and has nothing to do with the v1 in the URL: the two move independently.","catchingUp":"GET /api/v1/changes?since=<ms> answers with the projects, lists and cards that moved since that moment, in the same shape a full read uses, plus `taskIds`: the id of every card it is holding for you. That is open cards plus anything finished in the last 30 days; send ?completed=all for every finished card ever, or ?completed=none for open cards only, and the reply says which in `window`. Diff that list against what you hold to learn what was deleted, however it was deleted. The reply also carries `now`, the server's own clock: send it back as the next `since` and no clock of yours is ever involved. ?manifest=changed narrows `taskIds` to the projects in the reply, which is much cheaper and is safe as long as you also drop anything you hold finished before the `cutoff` in the reply; the default is the whole list. Comparison is inclusive, so a row written in the same millisecond is never skipped and you may see a row you already have. Leave `since` off for everything.","fieldNames":"Unknown fields are refused with 422 naming them, on every write, so a typo is never mistaken for a no-op. The reply lists the fields that route does accept. Every create also takes an optional `id`, so a client can name what it makes and keep its own ids.","concurrency":"Any writable field accepts either a bare value or {before, after}. With {before, after} the write only lands while the field still holds `before`, otherwise it answers 409 with the current value.","deletes":"Deleting is a recycle bin of a fixed size, not a timer. A delete sets a cancelled or deleted timestamp and drops out of reads, and your last 20 deletions can be brought back with PATCH {\"cancelledAt\": null} however long ago they happened. Anything older than that is destroyed the moment it falls out, which is also how deleting frees space. POST /api/v1/undo separately reverses the most recent change made by your own key, reaching back 20 changes.","endpoints":{"GET /api/health":"no key needed: {ok, mode}","GET /api/v1/projects":"every project, with its lists, cards, tags, checklists and comments. OPEN CARDS ONLY unless you send ?completed=all for everything, or ?completed=recent for open cards plus the last 30 days of finished ones. ?name=<board> narrows it to one, folding case.","GET /api/v1/changes?since=":"only what moved since a moment, plus the id of every card it holds for you. Open cards plus the last 30 days of finished ones unless you send ?completed=all","GET /api/v1/export":"the same document as a downloadable file","POST /api/v1/projects":"{id?, name, columns?, folder?, position?, pinnedAt?, photo?}","GET /api/v1/projects/:id":"one project, in the same shape a whole account read uses","PATCH|DELETE /api/v1/projects/:id":"{name?, folder?, position?, archivedAt?, pinnedAt?, photo?}","POST /api/v1/columns":"{id?, projectId, name, description?, position?, archivedAt?}","PATCH|DELETE /api/v1/columns/:id":"{name?, description?, position?, archivedAt?}","POST /api/v1/tasks":"{id?, columnId, title, description?, priority?, effort?, deadline?, position?, tags?, completedAt?, cancelledAt?}, or {tasks: [...]} to create up to 200 at once, all or nothing","GET|PATCH|DELETE /api/v1/tasks/:id":"every task field, one or many at a time. Completing or deleting a parent takes its open subtasks with it.","PATCH /api/v1/tasks/positions":"{positions: [{id, position}, ...]} — up to 1000 cards moved in one request, across any lists. Unknown ids are ignored. Not guarded and not undoable.","POST /api/v1/tasks/:id/tags|comments":"{id?, text}","POST /api/v1/tasks/:id/checklist":"{id?, text, done?, position?}, or {items: [...]} to append a whole list at once, all or nothing","POST /api/v1/tasks/:id/checklist/order":"{ids} — the whole checklist in the order it should read. Anything left out keeps its place at the end. Not undoable.","PATCH /api/v1/checklist/:id":"{text?, done?}","DELETE /api/v1/checklist/:id, /api/v1/tags/:id, /api/v1/comments/:id":"remove one, immediately and for good","GET /api/v1/usage":"storage used, the limit, and what a purge would give back","POST /api/v1/purge":"destroy every deleted card now, including the recoverable ones","POST /api/v1/undo":"reverse your own last change","GET|POST /api/v1/keys, DELETE /api/v1/keys/:id":"list, add and revoke keys on this account","POST /api/v1/live/ticket":"a 60 second ticket for the change socket at /api/v1/live","GET /api/v1/live":"a WebSocket that sends {type: \"changed\"} when this account changes anywhere"},"fields":{"priority":"1 to 4, where 1 is highest. Out of range is clamped.","effort":["easy","medium","hard",null],"deadline":"YYYY-MM-DD or null. A date that does not exist is refused.","order":"read-only: the index of a task among its project's live tasks. Write `position` to reorder.","completedAt":"a millisecond timestamp, or null for not done. Accepted when creating a task as well as when editing one, so an export can be put back exactly as it left.","archivedAt":"on a list: a millisecond timestamp, or null for a live list. An archived list keeps its position and every card in it; it is set aside, not deleted. Accepted when creating a list as well as when editing one.","photo":"on a project: a small square image as a base64 data URL (WebP, JPEG or PNG), at most 24,576 characters, or null to remove it. The website shows it cropped to a circle beside the name.","updatedAt":"read-only, on a project: when anything inside it last changed, including its lists and cards.","deletedAt":"only appears in a changes reply. A project or list carrying a timestamp here has been deleted; drop it and everything under it.","location":"free text on a task, a note about where. There is no geofence and no arrival reminder behind it; it is a field you fill in and read.","taskIds":"only appears in a changes reply. Every live card id on the account. Anything you hold that is missing from it is gone."},"limits":{"titleCharacters":250,"locationCharacters":200,"projectAndColumnNameCharacters":40,"descriptionCharacters":262144,"commentCharacters":16384,"checklistItemCharacters":500,"sectionDescriptionCharacters":2000,"projectsPerAccount":100,"columnsPerProject":20,"tagsPerTask":5,"checklistItemsPerTask":100,"commentsPerTask":200,"readsPerKeyPerDay":9000,"writesPerKeyPerDay":3000,"burstRequestsPerTenSecondsOnKeylessDiscovery":10,"dailyAllowanceResetsAtMidnightIn":"America/New_York","dailyAllowanceNote":"The read figure counts every request, not only GETs, so it is the total budget for a key in a day; a write is counted against both. The day rolls over at midnight America/New_York, not UTC.","undoStepsPerKey":20,"recoverableDeletesPerAccount":20,"storageBytesPerAccount":16777216,"note":"Short display fields are truncated to fit. Longer prose is refused with 422 rather than silently cut. Deleted items still count towards storage until they are purged."},"storage":"GET /api/v1/usage shows what this account is using. Deleting is also how space is freed: your last 20 deletions stay recoverable and keep their space, and anything older is destroyed the moment it falls out of that window. POST /api/v1/purge empties the recoverable ones too. Over the limit, reads and deletes keep working and only writes that would grow the account are refused with 413.","errors":"400 unreadable body, 401 key problem, 404 not yours or not there, 409 someone else changed it first, 422 the value or the limit, 429 daily allowance. Every error answers {ok: false, error}. The one exception is 403, which is not us: a request carrying no Authorization header at all is refused at the edge before this code runs, and answers an HTML page rather than JSON. If you get a 403 with HTML in it, you left the header off. Send the header and you will get a normal 401 if the key itself is the problem. There is no rate limit on authenticated calls; your daily allowance is the only ceiling."}