{"openapi":"3.1.0","info":{"title":"Caliu API","version":"1.0.0","description":"API-first notes app. Access your notes programmatically using an API key.\n\nCreate an API key in the app settings, then pass it as `Authorization: Bearer caliu_xxx` header.","contact":{"name":"Caliu","url":"https://caliuapp.com"}},"servers":[{"url":"https://api.caliuapp.com","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key (caliu_xxx) or session token"},"cookieAuth":{"type":"apiKey","in":"cookie","name":"caliu_session"}},"schemas":{"User":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","nullable":true,"format":"email"},"name":{"type":"string","nullable":true},"avatarUrl":{"type":"string","nullable":true},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"drawing_enabled":{"type":"boolean"}},"required":["id","email","name","createdAt","updatedAt"]},"UpdateMeRequest":{"type":"object","properties":{"name":{"type":"string","nullable":true,"maxLength":100}},"required":["name"]},"Note":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"},"isPinned":{"type":"boolean"},"isEncrypted":{"type":"boolean"},"encryptionHint":{"type":"string","nullable":true},"wordCount":{"type":"number"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"deletedAt":{"type":"string","nullable":true},"archivedAt":{"type":"string","nullable":true},"sharedAt":{"type":"string","nullable":true},"sortOrder":{"type":"number","nullable":true},"rev":{"type":"number","description":"Content revision: one more for every accepted content change. Send it back as `base_rev` on `PUT /notes/{id}`."},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}},"role":{"type":"string","enum":["editor","viewer"],"description":"The requester's role, on notes shared with them (get and search)."},"ownerId":{"type":"string","description":"The owner, on notes shared with the requester."}},"required":["id","title","content","isPinned","isEncrypted","encryptionHint","wordCount","createdAt","updatedAt","deletedAt","archivedAt","sharedAt","sortOrder"]},"OutgoingLink":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string","nullable":true},"updatedAt":{"type":"string","nullable":true},"private":{"type":"boolean"}},"required":["id","title","updatedAt","private"]},"Tag":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parentId":{"type":"string","nullable":true},"handle":{"type":"string","description":"Full hierarchical handle as written in note content, e.g. \"#work/project\" or \"#my project#\" (multi-word tags carry a closing hash)"},"color":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"isPinned":{"type":"boolean","description":"Pinned tags sort first in clients"},"noteCount":{"type":"number"},"createdAt":{"type":"string"}},"required":["id","name","parentId","color","icon","createdAt"]},"Reminder":{"type":"object","properties":{"id":{"type":"string"},"noteId":{"type":"string"},"triggerAt":{"type":"string"},"recurrence":{"type":"string","nullable":true},"snoozedUntil":{"type":"string","nullable":true},"firedAt":{"type":"string","nullable":true},"dismissedAt":{"type":"string","nullable":true},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","noteId","triggerAt","recurrence","snoozedUntil","firedAt","dismissedAt","createdAt","updatedAt"]},"Attachment":{"type":"object","properties":{"id":{"type":"string"},"filename":{"type":"string"},"content_type":{"type":"string"},"size_bytes":{"type":"number"},"r2_key":{"type":"string"},"created_at":{"type":"string"}},"required":["id","filename","content_type","size_bytes","r2_key","created_at"]},"UploadLink":{"type":"object","properties":{"upload_url":{"type":"string","description":"PUT the raw file bytes here, with a Content-Length. Single use, expires after an hour."},"method":{"type":"string","enum":["PUT"]},"attachment_id":{"type":"string"},"download_url":{"type":"string","description":"Where the file will be, for note markdown."},"content_type":{"type":"string"},"expires_at":{"type":"string"},"max_bytes":{"type":"number"}},"required":["upload_url","method","attachment_id","download_url","content_type","expires_at","max_bytes"]},"ApiKey":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"First 8 chars of the key for identification"},"lastUsedAt":{"type":"string","nullable":true},"expiresAt":{"type":"string","nullable":true},"createdAt":{"type":"string"}},"required":["id","name","prefix","createdAt"]}},"parameters":{}},"paths":{"/auth/me":{"get":{"tags":["Auth"],"summary":"Get current user","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"User info","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"tags":["Auth"],"summary":"Update current user","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMeRequest"}}}},"responses":{"200":{"description":"Updated user info","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes":{"get":{"tags":["Notes"],"summary":"List notes","description":"List notes with pagination, search, and filtering. Use `since` for incremental sync.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Full-text search query"},"required":false,"description":"Full-text search query","name":"q","in":"query"},{"schema":{"type":"string","description":"Filter by tag ID"},"required":false,"description":"Filter by tag ID","name":"tag","in":"query"},{"schema":{"type":"string","description":"Show only pinned notes"},"required":false,"description":"Show only pinned notes","name":"pinned","in":"query"},{"schema":{"type":"string","description":"Show only untagged notes"},"required":false,"description":"Show only untagged notes","name":"untagged","in":"query"},{"schema":{"type":"string","description":"ISO timestamp for incremental sync"},"required":false,"description":"ISO timestamp for incremental sync","name":"since","in":"query"},{"schema":{"type":"string","description":"Max notes to return (1-200, default 50)"},"required":false,"description":"Max notes to return (1-200, default 50)","name":"limit","in":"query"},{"schema":{"type":"string","description":"Cursor for keyset pagination"},"required":false,"description":"Cursor for keyset pagination","name":"cursor","in":"query"}],"responses":{"200":{"description":"List of notes","content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/Note"}},"nextCursor":{"type":"string","nullable":true}},"required":["notes","nextCursor"]}}}},"400":{"description":"Search query too long","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"tags":["Notes"],"summary":"Create note","description":"Create a new note. Accepts optional `id` for offline-created notes.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Client-provided ID for offline-created notes"},"content":{"type":"string","description":"Note content in markdown. Title is derived from the first line."},"is_pinned":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"},"description":"Tag IDs to associate. When provided, hashtag auto-extraction is skipped."},"auto_sync_tags":{"type":"boolean","description":"Opt-in: extract #hashtags (including multi-word `#like this#`) from content and link tags server-side. Default false for backward compatibility."},"auto_link_notes":{"type":"boolean","description":"Opt-in: resolve bare [[Note Title]] wiki links against your existing notes and rewrite them to the canonical [[id|Title]] form. Unresolved titles are left as-is. Default false for backward compatibility."}}}}}},"responses":{"201":{"description":"Note created","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"]}}}},"409":{"description":"Note id already in use","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/import":{"post":{"tags":["Notes"],"summary":"Import notes","description":"Bulk import notes from .md, .zip, .bear2bk, .textpack, or .json files. Max 500 notes.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"nullable":true}}}}}},"responses":{"200":{"description":"Import results","content":{"application/json":{"schema":{"type":"object","properties":{"imported":{"type":"number"},"skipped":{"type":"number"},"warnings":{"type":"array","items":{"type":"string"}},"source":{"type":"string"}},"required":["imported","skipped","warnings"]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/trash":{"get":{"tags":["Notes"],"summary":"List trashed notes","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Soft-deleted notes","content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/Note"}}},"required":["notes"]}}}}}},"delete":{"tags":["Notes"],"summary":"Empty trash","description":"Permanently delete all soft-deleted notes.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Trash emptied","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}}}}},"/notes/archive":{"get":{"tags":["Notes"],"summary":"List archived notes","description":"List archived notes (most-recently-archived first, capped at 100). Archived notes are hidden from all other listings but never auto-removed. Keyset pagination is a planned follow-up; mirrors GET /notes/trash today.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Archived notes","content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"array","items":{"$ref":"#/components/schemas/Note"}}},"required":["notes"]}}}}}}},"/notes/{id}":{"get":{"tags":["Notes"],"summary":"Get note","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Note details","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"tags":["Notes"],"summary":"Update note","description":"Update a note. Supports optimistic concurrency via `expected_updated_at`.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","description":"Note content in markdown. Title is derived from the first line."},"is_pinned":{"type":"boolean"},"expected_updated_at":{"type":"string","description":"For optimistic concurrency — rejected if server version is newer"},"base_rev":{"type":"integer","minimum":0,"description":"The `rev` of the note your content was edited from. With it, edits made since are kept and merged instead of answering 409."},"auto_sync_tags":{"type":"boolean","description":"Opt-in: when content is provided, extract #hashtags and replace the note's tags. Default false for backward compatibility — existing clients managing tags via /tags endpoints are unaffected."},"auto_link_notes":{"type":"boolean","description":"Opt-in: when content is provided, resolve bare [[Note Title]] wiki links against your existing notes and rewrite them to the canonical [[id|Title]] form. Unresolved titles are left as-is. Default false for backward compatibility."}}}}}},"responses":{"200":{"description":"Note updated","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict — note modified by another client","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"note":{"$ref":"#/components/schemas/Note"}},"required":["error","note"]}}}},"413":{"description":"Content above 1,000,000 UTF-8 bytes","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"tags":["Notes"],"summary":"Delete note (soft)","description":"Soft-delete a note. It can be restored from trash.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Note deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/history":{"get":{"tags":["Notes"],"summary":"Get note version history","description":"Return past snapshots of a note projected from the sync log, ordered newest first. Updates within a 5-minute compaction window collapse into one entry. `delete` (hard-delete) operations are excluded — they have no body. Use `cursor` (the `nextCursor` from the previous response) and `limit` (default 50, max 100) to paginate.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Sync log version of the last entry from the previous page. Pass `nextCursor` from the prior response."},"required":false,"description":"Sync log version of the last entry from the previous page. Pass `nextCursor` from the prior response.","name":"cursor","in":"query"},{"schema":{"type":"string","description":"Max entries to return (1-100, default 50)."},"required":false,"description":"Max entries to return (1-100, default 50).","name":"limit","in":"query"}],"responses":{"200":{"description":"Page of past versions","content":{"application/json":{"schema":{"type":"object","properties":{"versions":{"type":"array","items":{"type":"object","properties":{"version":{"type":"number"},"operation":{"type":"string","enum":["create","update","soft-delete","restore"]},"createdAt":{"type":"string"},"title":{"type":"string"},"content":{"type":"string"},"isEncrypted":{"type":"boolean"},"encryptionHint":{"type":"string","nullable":true},"editedBy":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string","nullable":true}},"required":["userId","name"],"description":"Who made this version, on a shared note"}},"required":["version","operation","createdAt","title","content","isEncrypted","encryptionHint"]}},"nextCursor":{"type":"integer","nullable":true}},"required":["versions","nextCursor"]}}}},"402":{"description":"Version history requires Caliu Pro","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Note not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/restore":{"post":{"tags":["Notes"],"summary":"Restore note from trash","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Note restored","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"]}}}},"404":{"description":"Not found in trash","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/archive":{"post":{"tags":["Notes"],"summary":"Archive note","description":"Archive a note. Archived notes are hidden from all listings but never auto-removed, and keep their tags so they can be unarchived losslessly.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Note archived","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/unarchive":{"post":{"tags":["Notes"],"summary":"Unarchive note","description":"Restore a note from the archive back to the active list.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Note unarchived","content":{"application/json":{"schema":{"type":"object","properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"]}}}},"404":{"description":"Not found in archive","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/permanent":{"delete":{"tags":["Notes"],"summary":"Permanently delete note","description":"Hard-delete a note. This cannot be undone.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Note permanently deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/backlinks":{"get":{"tags":["Notes"],"summary":"Get backlinks","description":"Get notes that link to this note via [[wiki links]].","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Backlinks","content":{"application/json":{"schema":{"type":"object","properties":{"backlinks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"updated_at":{"type":"string"}},"required":["id","title","updated_at"]}}},"required":["backlinks"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/links":{"get":{"tags":["Notes"],"summary":"Get outgoing links","description":"Notes this note mentions. A mention of a note the reader cannot open comes back as a private placeholder: `{ id, private: true, title: null, updatedAt: null }`, rendered as a \"private note\" chip. Mentions of notes that do not exist on the server are left out.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Outgoing links","content":{"application/json":{"schema":{"type":"object","properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/OutgoingLink"}}},"required":["links"]}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/tags":{"post":{"tags":["Notes"],"summary":"Add tag to note","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tagId":{"type":"string"}},"required":["tagId"]}}}},"responses":{"200":{"description":"Tag added","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/tags/sync":{"post":{"tags":["Notes"],"summary":"Sync note tags","description":"Replace all tags from a hashtag list. Upserts tags and removes orphans.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated tags","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]}}},"required":["tags"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/notes/{id}/tags/{tagId}":{"delete":{"tags":["Notes"],"summary":"Remove tag from note","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Note ID"},"required":true,"description":"Note ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Tag ID"},"required":true,"description":"Tag ID","name":"tagId","in":"path"}],"responses":{"200":{"description":"Tag removed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/tags":{"get":{"tags":["Tags"],"summary":"List tags","description":"List all tags for the current user with note counts.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Max tags to return (1-500, default 200)"},"required":false,"description":"Max tags to return (1-500, default 200)","name":"limit","in":"query"},{"schema":{"type":"string","description":"Cursor for pagination (tag name)"},"required":false,"description":"Cursor for pagination (tag name)","name":"cursor","in":"query"}],"responses":{"200":{"description":"List of tags","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"nextCursor":{"type":"string","nullable":true}},"required":["tags","nextCursor"]}}}}}},"post":{"tags":["Tags"],"summary":"Create tag","description":"Create a new tag. Restores soft-deleted tags with the same name.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Tag name"},"parentId":{"type":"string"},"color":{"type":"string"},"icon":{"type":"string"}},"required":["name"]}}}},"responses":{"201":{"description":"Tag created","content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"$ref":"#/components/schemas/Tag"}},"required":["tag"]}}}},"409":{"description":"Tag already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/tags/{id}":{"put":{"tags":["Tags"],"summary":"Update tag","description":"Update tag name, color, or icon.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"color":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"isPinned":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Tag updated","content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"$ref":"#/components/schemas/Tag"}},"required":["tag"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Name conflict","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"tags":["Tags"],"summary":"Delete tag","description":"Soft-delete a tag. Preserves color/icon for potential restoration.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Tag deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/reminders":{"get":{"tags":["Reminders"],"summary":"List reminders","description":"List all reminders for the current user from the derived projection.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"List of reminders","content":{"application/json":{"schema":{"type":"object","properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/Reminder"}}},"required":["reminders"]}}}}}},"post":{"tags":["Reminders"],"summary":"Create reminder","description":"Create a reminder for a note. Multiple reminders per note are supported.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Client-provided ID for offline-created reminders"},"noteId":{"type":"string","description":"Note ID to attach the reminder to"},"triggerAt":{"type":"string","format":"date-time","description":"ISO 8601 datetime when the reminder should fire"},"recurrence":{"type":"string","description":"RRULE string for recurring reminders (e.g. FREQ=DAILY)"}},"required":["noteId","triggerAt"]}}}},"responses":{"201":{"description":"Reminder created","content":{"application/json":{"schema":{"type":"object","properties":{"reminder":{"$ref":"#/components/schemas/Reminder"}},"required":["reminder"]}}}},"404":{"description":"Note not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/reminders/note/{noteId}":{"get":{"tags":["Reminders"],"summary":"Get reminders for note","description":"Get the reminders attached to a specific note.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Note ID","example":"abc123"},"required":true,"description":"Note ID","name":"noteId","in":"path"}],"responses":{"200":{"description":"Reminders for the note","content":{"application/json":{"schema":{"type":"object","properties":{"reminders":{"type":"array","items":{"$ref":"#/components/schemas/Reminder"}}},"required":["reminders"]}}}},"404":{"description":"Note not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/reminders/{id}":{"put":{"tags":["Reminders"],"summary":"Update reminder","description":"Update a reminder trigger time, recurrence, or snooze.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"triggerAt":{"type":"string","format":"date-time","description":"New trigger time (ISO 8601)"},"recurrence":{"type":"string","nullable":true,"description":"RRULE string or null to remove recurrence"},"snoozedUntil":{"type":"string","nullable":true,"format":"date-time","description":"Snooze until this time (ISO 8601) or null to clear"},"dismissedAt":{"type":"string","nullable":true,"format":"date-time","description":"Dismissed at this time (ISO 8601) or null to reactivate"}}}}}},"responses":{"200":{"description":"Reminder updated","content":{"application/json":{"schema":{"type":"object","properties":{"reminder":{"$ref":"#/components/schemas/Reminder"}},"required":["reminder"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"tags":["Reminders"],"summary":"Delete reminder","description":"Delete a reminder.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"Reminder deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/attachments":{"post":{"tags":["Attachments"],"summary":"Upload attachment","description":"Upload a user-owned file attachment. Any file type, max 100MB.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"nullable":true},"id":{"type":"string"}}}}}},"responses":{"200":{"description":"Idempotent replay - attachment already exists","content":{"application/json":{"schema":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/Attachment"}},"required":["attachment"]}}}},"201":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/Attachment"}},"required":["attachment"]}}}},"400":{"description":"Invalid file or id","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"402":{"description":"Free storage limit reached (Caliu Pro required)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"id already owned by another user","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Storage failure","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/attachments/uploads":{"post":{"tags":["Attachments"],"summary":"Create upload link","description":"Mint a one-time link to upload one file with a plain PUT of its bytes (no multipart, no base64). Single use, valid for an hour, max 100MB.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255},"content_type":{"type":"string","pattern":"^[A-Za-z0-9!#$&^_.+-]{1,64}\\/[A-Za-z0-9!#$&^_.+-]{1,64}(;\\s*[A-Za-z0-9_.-]+=[A-Za-z0-9_.-]+)*$","description":"Defaults to application/octet-stream."}},"required":["filename"]}}}},"responses":{"201":{"description":"Upload link created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadLink"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/attachments/uploads/{token}":{"put":{"tags":["Attachments"],"summary":"Upload to a link","description":"Send the raw bytes of the file an upload link was made for. The token in the path is the credential: no Authorization header.","parameters":[{"schema":{"type":"string","description":"From upload_url"},"required":true,"description":"From upload_url","name":"token","in":"path"}],"responses":{"201":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/Attachment"}},"required":["attachment"]}}}},"402":{"description":"Free storage limit reached (Caliu Pro required)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Link unknown, used or expired","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"411":{"description":"Content-Length missing","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"File over 100MB","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"500":{"description":"Storage failure","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/attachments/{attId}":{"delete":{"tags":["Attachments"],"summary":"Delete attachment","description":"Remove a user-owned file attachment from R2 storage and database.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Attachment ID"},"required":true,"description":"Attachment ID","name":"attId","in":"path"}],"responses":{"200":{"description":"Attachment deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/attachments/{attId}/download":{"get":{"tags":["Attachments"],"summary":"Download attachment","description":"Stream a file attachment from R2 storage. Its uploader, plus anyone who can open a note referencing it that its owner or one of its editors uploaded it to; a note on a link makes it downloadable without authentication.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Attachment ID"},"required":true,"description":"Attachment ID","name":"attId","in":"path"}],"responses":{"200":{"description":"File stream"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/export/notes/{id}":{"get":{"tags":["Export"],"summary":"Export single note","description":"Export a single note as markdown or JSON.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"},{"schema":{"type":"string","enum":["markdown","json"],"description":"Export format (default: markdown)"},"required":false,"description":"Export format (default: markdown)","name":"format","in":"query"}],"responses":{"200":{"description":"Note export (markdown text or JSON)"},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/export/notes":{"get":{"tags":["Export"],"summary":"Export all notes","description":"Export all notes as JSON or a ZIP of markdown files.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["json","markdown"],"description":"Export format (default: json)"},"required":false,"description":"Export format (default: json)","name":"format","in":"query"}],"responses":{"200":{"description":"Notes export (JSON or ZIP of markdown files)"}}}},"/api-keys":{"get":{"tags":["API Keys"],"summary":"List API keys","description":"List all API keys for the current user. Only the prefix is returned, never the full key.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"object","properties":{"apiKeys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}},"required":["apiKeys"]}}}}}},"post":{"tags":["API Keys"],"summary":"Create API key","description":"Create a new API key. The full key is returned ONCE at creation — store it securely.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for this API key"}},"required":["name"]}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"key":{"type":"string","description":"Full API key — shown ONCE at creation, never stored"}},"required":["key"]}]}},"required":["apiKey"]}}}},"402":{"description":"Free key limit reached (Caliu Pro required)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/api-keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Revoke API key","description":"Permanently delete an API key. This cannot be undone.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"}],"responses":{"200":{"description":"API key revoked","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/devices":{"get":{"tags":["Devices"],"summary":"List logged-in devices","description":"List all active sessions for the current user. The session matching the caller's own credentials is flagged with `current: true`. API-key callers see all rows as `current: false`.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"List of devices","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"platform":{"type":"string","nullable":true},"user_agent":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"last_used_at":{"type":"string"},"created_at":{"type":"string"},"current":{"type":"boolean"}},"required":["id","platform","user_agent","country","city","last_used_at","created_at","current"]}}},"required":["devices"]}}}}}}},"/devices/{id}":{"delete":{"tags":["Devices"],"summary":"Revoke a device session","description":"Revoke (sign out) a device by deleting its session. Self-revoke (deleting your own current session) requires `?force=1` to prevent accidental self-logout.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"parameters":[{"schema":{"type":"string","description":"Resource ID","example":"abc123"},"required":true,"description":"Resource ID","name":"id","in":"path"},{"schema":{"type":"string","description":"Set to \"1\" to confirm revoking the caller's own session"},"required":false,"description":"Set to \"1\" to confirm revoking the caller's own session","name":"force","in":"query"}],"responses":{"200":{"description":"Device revoked","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"400":{"description":"Self-revoke without ?force=1, or attempt to revoke a non-session device from API-key auth","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/subscription":{"get":{"tags":["Subscription"],"summary":"Get current entitlement","description":"The signed-in user's Caliu Pro entitlement. `isPro` is the single value clients gate on.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Entitlement","content":{"application/json":{"schema":{"type":"object","properties":{"isPro":{"type":"boolean"},"source":{"type":"string","enum":["grandfathered","app_store","play_store","stripe","promotional","none"]},"expiresAt":{"type":"string","nullable":true},"willRenew":{"type":"boolean"},"plan":{"type":"string","nullable":true,"enum":["subscription","lifetime",null],"description":"What backs a paid entitlement: a renewing `subscription` or the one-time `lifetime` purchase. Both unlock the same features. `null` when Pro comes from no store purchase."},"noteLimit":{"type":"integer","nullable":true,"description":"Free note limit for this account (notes outside the trash), or `null` for unlimited. A client-side guard only: note writes and sync are never rejected by the server."},"noteCount":{"type":"integer","nullable":true,"description":"Notes that count toward `noteLimit`, or `null` when `noteLimit` is. The apps count locally; the MCP server reads this to guard `create_note`."}},"required":["isPro","source","expiresAt","willRenew","plan","noteLimit","noteCount"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/subscription/reconcile":{"post":{"tags":["Subscription"],"summary":"Re-read this user's entitlement from the store","description":"Asks the billing provider for the current entitlement and stores it. Used after sign-in when a purchase was made before an account existed. Takes no body: the account is the authenticated caller.","security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Entitlement after reconciliation","content":{"application/json":{"schema":{"type":"object","properties":{"reconciled":{"type":"boolean"},"entitlement":{"type":"object","properties":{"isPro":{"type":"boolean"},"source":{"type":"string","enum":["grandfathered","app_store","play_store","stripe","promotional","none"]},"expiresAt":{"type":"string","nullable":true},"willRenew":{"type":"boolean"},"plan":{"type":"string","nullable":true,"enum":["subscription","lifetime",null],"description":"What backs a paid entitlement: a renewing `subscription` or the one-time `lifetime` purchase. Both unlock the same features. `null` when Pro comes from no store purchase."},"noteLimit":{"type":"integer","nullable":true,"description":"Free note limit for this account (notes outside the trash), or `null` for unlimited. A client-side guard only: note writes and sync are never rejected by the server."},"noteCount":{"type":"integer","nullable":true,"description":"Notes that count toward `noteLimit`, or `null` when `noteLimit` is. The apps count locally; the MCP server reads this to guard `create_note`."}},"required":["isPro","source","expiresAt","willRenew","plan","noteLimit","noteCount"]}},"required":["reconciled","entitlement"]}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}}}}