{"components":{"responses":{},"schemas":{"User":{"properties":{"analytics_id":{"description":"Keyed pseudonymous identifier (HMAC of the user's email) for client-side analytics identify calls; null when the user opted out. See Engram.Analytics.client_id/1.","nullable":true,"type":"string"},"analytics_opt_out":{"description":"The user objected to analytics; the client must send nothing.","type":"boolean"},"display_name":{"nullable":true,"type":"string"},"email":{"format":"email","type":"string"},"id":{"format":"uuid","type":"string"},"locale":{"description":"UI language as a BCP 47 tag, as last reported by a client.","example":"pt-BR","nullable":true,"type":"string"},"role":{"example":"user","type":"string"}},"required":["id","email","analytics_id","analytics_opt_out"],"title":"User","type":"object"},"NoteLink":{"properties":{"alias":{"description":"Display text: `[[target|alias]]`'s alias, or `[label](target)`'s label.","nullable":true,"type":"string"},"anchor":{"description":"Heading/block reference: `[[target#anchor]]` or `[x](target#anchor)`.","nullable":true,"type":"string"},"dangling":{"description":"True when the target could not be resolved.","type":"boolean"},"link_type":{"description":"\"wikilink\" or \"embed\" — whether the link EMBEDS its target. Not the source syntax; markdown-form links carry these same two values.","type":"string"},"target_attachment_id":{"format":"uuid","nullable":true,"type":"string"},"target_note_id":{"format":"uuid","nullable":true,"type":"string"},"target_path":{"description":"Resolved note path.","nullable":true,"type":"string"},"target_text":{"description":"The link target, percent-DECODED. `[[Target]]` and `[x](Target.md)` both yield `Target`/`Target.md` here — not the literal source bytes.","type":"string"}},"required":["target_text","link_type","dangling"],"title":"NoteLink","type":"object"},"UpdateProfileRequest":{"example":{"display_name":"Ada Lovelace","locale":"en"},"properties":{"analytics_opt_out":{"description":"true objects to product analytics and erases the user's PostHog person; false withdraws the objection","type":"boolean"},"display_name":{"nullable":true,"type":"string"},"locale":{"description":"BCP 47 language tag, stored in canonical casing","example":"pt-BR","maxLength":35,"pattern":"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$","type":"string"}},"title":"UpdateProfileRequest","type":"object"},"VaultsResponse":{"properties":{"access_levels":{"description":"True when this server supports per-vault access levels and `mcp:read` on OAuth grants and API keys. Absent on servers that predate them.","type":"boolean"},"suggested_device_name":{"description":"Present only when a `user_code` query param is supplied. The name the plugin suggested for its device (hostname or platform), for a default connection label. Null when the code is invalid or the plugin sent none.","nullable":true,"type":"string"},"suggested_vault_name":{"description":"Present only when a `user_code` query param is supplied (device-link flow). May be null for a perfectly valid code — check `user_code_valid`, not this.","nullable":true,"type":"string"},"user_code_valid":{"description":"Present only when a `user_code` query param is supplied. True when the code is real, still pending, unexpired, and claimable by the calling user. This is the validity signal; a null `suggested_vault_name` is not.","type":"boolean"},"vaults":{"items":{"$ref":"#/components/schemas/Vault"},"type":"array"}},"required":["vaults"],"title":"VaultsResponse","type":"object"},"LogIngestRequest":{"properties":{"logs":{"items":{"$ref":"#/components/schemas/LogInput"},"type":"array"}},"required":["logs"],"title":"LogIngestRequest","type":"object"},"ApiKeysResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/ApiKeyMeta"},"type":"array"}},"required":["keys"],"title":"ApiKeysResponse","type":"object"},"Folder":{"properties":{"count":{"type":"integer"},"id":{"format":"uuid","nullable":true,"type":"string"},"name":{"type":"string"},"parent_id":{"format":"uuid","nullable":true,"type":"string"}},"required":["name"],"title":"Folder","type":"object"},"UpdateVaultRequest":{"example":{"is_default":true,"name":"Research (renamed)"},"properties":{"is_default":{"type":"boolean"},"name":{"type":"string"}},"title":"UpdateVaultRequest","type":"object"},"NoteResponse":{"properties":{"note":{"$ref":"#/components/schemas/Note"}},"required":["note"],"title":"NoteResponse","type":"object"},"ManifestEntry":{"properties":{"content_hash":{"nullable":true,"type":"string"},"crdt_head":{"description":"CRDT head fingerprint (notes only) — equality with the client's recorded head proves a live-bound doc is converged without content transfer.","nullable":true,"type":"string"},"id":{"description":"Stable note/attachment id — lets a client reconcile its path↔id map.","format":"uuid","type":"string"},"path":{"type":"string"},"seq":{"description":"Vault-global change sequence of the row's last write — integer-diff validation hook (a client whose recorded seq is lower is behind).","nullable":true,"type":"integer"}},"required":["id","path"],"title":"ManifestEntry","type":"object"},"MovedCount":{"properties":{"moved":{"example":3,"type":"integer"}},"required":["moved"],"title":"MovedCount","type":"object"},"BatchItemError":{"properties":{"error":{"example":"conflict","type":"string"},"item_path":{"type":"string"}},"required":["error"],"title":"BatchItemError","type":"object"},"DeletedFlag":{"properties":{"deleted":{"example":true,"type":"boolean"}},"required":["deleted"],"title":"DeletedFlag","type":"object"},"LogRecord":{"properties":{"category":{"nullable":true,"type":"string"},"conn_id":{"nullable":true,"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"device_id":{"nullable":true,"type":"string"},"forced":{"nullable":true,"type":"boolean"},"id":{"format":"uuid","type":"string"},"level":{"nullable":true,"type":"string"},"message":{"nullable":true,"type":"string"},"platform":{"nullable":true,"type":"string"},"plugin_version":{"nullable":true,"type":"string"},"stack":{"nullable":true,"type":"string"},"ts":{"format":"date-time","nullable":true,"type":"string"}},"title":"LogRecord","type":"object"},"FoldersResponse":{"properties":{"folders":{"items":{"$ref":"#/components/schemas/Folder"},"type":"array"}},"required":["folders"],"title":"FoldersResponse","type":"object"},"CreatePatRequest":{"example":{"name":"Claude Desktop"},"properties":{"default_access":{"description":"Level for every vault not listed in `vault_access`, including vaults you create later. Defaults to `none` when `vault_access` is given. `write` also lets the key create vaults. The key must reach at least one vault now or later.","enum":["none","read","write"],"type":"string"},"name":{"type":"string"},"vault_access":{"additionalProperties":{"enum":["none","read","write"],"type":"string"},"description":"Level per vault id: `none`, `read` (search and read) or `write` (also create, edit, rename and delete notes, and rename or delete the vault). Every id must be one of your vaults. Omit both this and `default_access` for a key on every vault with full write access.","type":"object"}},"required":["name"],"title":"CreatePatRequest","type":"object"},"AttachmentDeleted":{"properties":{"deleted":{"example":true,"type":"boolean"},"path":{"type":"string"}},"required":["deleted","path"],"title":"AttachmentDeleted","type":"object"},"AttachmentRenameResponse":{"properties":{"attachment":{"$ref":"#/components/schemas/AttachmentMeta"},"new_path":{"type":"string"},"old_path":{"type":"string"},"renamed":{"type":"boolean"}},"title":"AttachmentRenameResponse","type":"object"},"AttachmentsResponse":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/AttachmentMeta"},"type":"array"}},"required":["attachments"],"title":"AttachmentsResponse","type":"object"},"ManifestResponse":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/ManifestEntry"},"type":"array"},"change_seq":{"description":"Current per-vault sequence watermark.","type":"integer"},"notes":{"items":{"$ref":"#/components/schemas/ManifestEntry"},"type":"array"},"total_attachments":{"type":"integer"},"total_notes":{"type":"integer"},"unchanged":{"description":"Present (true) only when `since_seq` equals the current watermark — the manifest body is omitted because nothing changed.","nullable":true,"type":"boolean"}},"required":["change_seq"],"title":"ManifestResponse","type":"object"},"AppendRequest":{"example":{"path":"Daily/2026-06-20.md","text":"\n- [ ] Ship the API docs coverage gate\n"},"properties":{"path":{"type":"string"},"text":{"type":"string"}},"required":["path","text"],"title":"AppendRequest","type":"object"},"Error":{"properties":{"errors":{"description":"Error detail — a message string or a field→messages map.","oneOf":[{"type":"string"},{"type":"object"}]}},"title":"Error","type":"object"},"ApiKeyMeta":{"properties":{"created_at":{"format":"date-time","nullable":true,"type":"string"},"id":{"format":"uuid","type":"string"},"last_used":{"format":"date-time","nullable":true,"type":"string"},"name":{"type":"string"}},"required":["id","name"],"title":"ApiKeyMeta","type":"object"},"MimeRejected":{"properties":{"error":{"example":"mime_not_allowed","type":"string"},"extension":{"nullable":true,"type":"string"},"mime_type":{"nullable":true,"type":"string"}},"required":["error"],"title":"MimeRejected","type":"object"},"ConnectionsList":{"items":{"$ref":"#/components/schemas/Connection"},"title":"ConnectionsList","type":"array"},"VaultTreeAttachment":{"properties":{"id":{"format":"uuid","type":"string"},"mime_type":{"nullable":true,"type":"string"},"mtime":{"description":"Client mtime (epoch seconds)","format":"float","type":"number"},"path":{"type":"string"},"size_bytes":{"type":"integer"},"updated_at":{"format":"date-time","type":"string"}},"required":["id","path","size_bytes","mtime","updated_at"],"title":"VaultTreeAttachment","type":"object"},"VaultDeleted":{"properties":{"deleted":{"example":true,"type":"boolean"},"id":{"format":"uuid","type":"string"}},"required":["deleted","id"],"title":"VaultDeleted","type":"object"},"SearchRequest":{"example":{"diversity":0.3,"limit":5,"mode":"hybrid","query":"omega-3 dosage","type":"Playbook"},"properties":{"created_after":{"description":"Only notes whose frontmatter created date is at or after this ISO 8601 instant.","format":"date-time","type":"string"},"created_before":{"description":"Only notes whose frontmatter created date is at or before this ISO 8601 instant.","format":"date-time","type":"string"},"cross_vault":{"description":"Pro plan only.","type":"boolean"},"diversity":{"description":"Result diversity (MMR). 0 = most relevant (default, tuned per plan); 1 = most varied.","maximum":1.0,"minimum":0.0,"type":"number"},"folder":{"type":"string"},"limit":{"description":"1..50, default 5","type":"integer"},"mode":{"enum":["keyword","vector","hybrid"],"type":"string"},"query":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array"},"type":{"description":"Filter to notes whose frontmatter `type` equals this value (case-insensitive).","type":"string"},"updated_after":{"description":"Only notes whose frontmatter timestamp is at or after this ISO 8601 instant.","format":"date-time","type":"string"},"updated_before":{"description":"Only notes whose frontmatter timestamp is at or before this ISO 8601 instant.","format":"date-time","type":"string"}},"required":["query"],"title":"SearchRequest","type":"object"},"DeleteAccountRequest":{"example":{"password":"correct horse battery staple"},"properties":{"password":{"format":"password","type":"string"}},"required":["password"],"title":"DeleteAccountRequest","type":"object"},"FolderNotesResponse":{"properties":{"notes":{"items":{"$ref":"#/components/schemas/NoteMeta"},"type":"array"}},"required":["notes"],"title":"FolderNotesResponse","type":"object"},"SearchResult":{"properties":{"folder":{"nullable":true,"type":"string"},"heading_path":{"nullable":true,"type":"string"},"id":{"format":"uuid","nullable":true,"type":"string"},"match_count":{"type":"integer"},"path":{"type":"string"},"score":{"format":"float","type":"number"},"snippet":{"type":"string"},"title":{"nullable":true,"type":"string"}},"title":"SearchResult","type":"object"},"UploadAttachmentRequest":{"example":{"content_base64":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","mime_type":"image/png","path":"assets/diagram.png"},"properties":{"content_base64":{"description":"Base64-encoded file bytes.","type":"string"},"mime_type":{"description":"Detected from path if omitted.","nullable":true,"type":"string"},"path":{"type":"string"}},"required":["path","content_base64"],"title":"UploadAttachmentRequest","type":"object"},"MessageError":{"properties":{"error":{"example":"not found","type":"string"}},"required":["error"],"title":"MessageError","type":"object"},"DeletedCount":{"properties":{"deleted":{"example":3,"type":"integer"}},"required":["deleted"],"title":"DeletedCount","type":"object"},"AttachmentBatchMoveRequest":{"example":{"paths":["assets/diagram.png","assets/logo.svg"],"target_folder":"archive/assets"},"properties":{"paths":{"items":{"type":"string"},"type":"array"},"target_folder":{"type":"string"}},"required":["paths","target_folder"],"title":"AttachmentBatchMoveRequest","type":"object"},"LogsResponse":{"properties":{"logs":{"items":{"$ref":"#/components/schemas/LogRecord"},"type":"array"}},"required":["logs"],"title":"LogsResponse","type":"object"},"AttachmentMeta":{"properties":{"content_hash":{"description":"Opaque content fingerprint. Stable for identical bytes and scoped to the owning account. Compare against a local copy to decide whether an upload is needed; re-uploading matching bytes is accepted but does no work.","nullable":true,"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"id":{"format":"uuid","nullable":true,"type":"string"},"mime_type":{"nullable":true,"type":"string"},"mtime":{"description":"Client mtime (epoch seconds)","format":"float","type":"number"},"path":{"example":"assets/diagram.png","type":"string"},"size_bytes":{"type":"integer"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["path"],"title":"AttachmentMeta","type":"object"},"Conflict":{"properties":{"error":{"example":"version_conflict","type":"string"},"server_note":{"$ref":"#/components/schemas/Note"}},"title":"Conflict","type":"object"},"BatchMoveFoldersRequest":{"example":{"ids":["3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f","4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f90"],"target_parent_id":"root"},"properties":{"ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"target_parent":{"description":"Destination parent folder path (alternative to target_parent_id; \"\" = top level). Use this to re-parent under a derived folder that has no marker.","type":"string"},"target_parent_id":{"description":"Parent folder UUID or \"root\".","type":"string"}},"required":["ids"],"title":"BatchMoveFoldersRequest","type":"object"},"RegisterVaultResponse":{"allOf":[{"$ref":"#/components/schemas/Vault"},{"properties":{"status":{"enum":["created","existing"],"type":"string"}},"type":"object"}],"title":"RegisterVaultResponse","type":"object"},"VaultTreeResponse":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/VaultTreeAttachment"},"type":"array"},"change_seq":{"description":"Current per-vault sequence watermark.","type":"integer"},"folders":{"items":{"$ref":"#/components/schemas/Folder"},"type":"array"},"notes":{"items":{"$ref":"#/components/schemas/VaultTreeNote"},"type":"array"}},"required":["change_seq"],"title":"VaultTreeResponse","type":"object"},"AppendResponse":{"properties":{"created":{"type":"boolean"},"note":{"$ref":"#/components/schemas/Note"},"path":{"type":"string"}},"title":"AppendResponse","type":"object"},"Backlink":{"properties":{"alias":{"nullable":true,"type":"string"},"anchor":{"nullable":true,"type":"string"},"source_note_id":{"format":"uuid","type":"string"},"source_path":{"nullable":true,"type":"string"},"source_title":{"nullable":true,"type":"string"}},"required":["source_note_id"],"title":"Backlink","type":"object"},"UpsertNoteRequest":{"example":{"content":"# Engram\n\nAn AI-powered personal knowledge base.\n","mtime":1.7189e9,"path":"Projects/Engram.md","tags":["project","ai"]},"properties":{"base_hash":{"description":"Compare-and-swap guard: the content_hash you last read for this note. If the note changed since, the write returns 409 instead of merging.","nullable":true,"type":"string"},"content":{"type":"string"},"folder":{"nullable":true,"type":"string"},"mtime":{"format":"float","type":"number"},"path":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array"},"title":{"nullable":true,"type":"string"},"version":{"nullable":true,"type":"integer"}},"required":["path","mtime"],"title":"UpsertNoteRequest","type":"object"},"FolderNamesResponse":{"properties":{"folders":{"items":{"properties":{"name":{"type":"string"}},"type":"object"},"type":"array"}},"required":["folders"],"title":"FolderNamesResponse","type":"object"},"VaultResponse":{"properties":{"vault":{"$ref":"#/components/schemas/Vault"}},"required":["vault"],"title":"VaultResponse","type":"object"},"Note":{"properties":{"content":{"type":"string"},"content_hash":{"nullable":true,"type":"string"},"description":{"description":"OKF frontmatter `description` field","nullable":true,"type":"string"},"fm_created":{"description":"OKF frontmatter `created`/`date` field","format":"date-time","nullable":true,"type":"string"},"fm_timestamp":{"description":"OKF frontmatter `timestamp`/`modified`/`updated` field","format":"date-time","nullable":true,"type":"string"},"folder":{"nullable":true,"type":"string"},"id":{"format":"uuid","nullable":true,"type":"string"},"links":{"description":"Outgoing link/embed edges, resolved. Wikilink and markdown syntax alike.","items":{"$ref":"#/components/schemas/NoteLink"},"type":"array"},"mtime":{"description":"Client mtime (epoch seconds)","format":"float","type":"number"},"parse_reason":{"description":"Present when parse_status is \"degraded\": {code, message, detail}","nullable":true,"type":"object"},"parse_status":{"description":"Frontmatter parse outcome","enum":["ok","degraded"],"nullable":true,"type":"string"},"path":{"example":"Projects/engram.md","type":"string"},"resource":{"description":"OKF frontmatter `resource` field","nullable":true,"type":"string"},"tags":{"items":{"type":"string"},"type":"array"},"title":{"nullable":true,"type":"string"},"type":{"description":"OKF frontmatter `type` field","nullable":true,"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"},"version":{"nullable":true,"type":"integer"}},"required":["path"],"title":"Note","type":"object"},"EmbedStatusResponse":{"properties":{"indexed":{"description":"Notes whose embedding matches current content.","type":"integer"},"pending":{"description":"Notes awaiting (re)embedding.","type":"integer"},"total":{"type":"integer"}},"required":["total","indexed","pending"],"title":"EmbedStatusResponse","type":"object"},"AttachmentResponse":{"properties":{"attachment":{"$ref":"#/components/schemas/AttachmentMeta"}},"required":["attachment"],"title":"AttachmentResponse","type":"object"},"UpdateAccessRequest":{"example":{"default_access":"none","vault_access":{"<vault id>":"write"}},"properties":{"default_access":{"description":"Level for every vault not listed, including vaults you create later. Defaults to `none`. The credential must reach at least one vault now or later.","enum":["none","read","write"],"type":"string"},"vault_access":{"additionalProperties":{"enum":["none","read","write"],"type":"string"},"description":"Level per vault id, replacing the credential's current levels. Every id must be one of your active vaults.","type":"object"}},"title":"UpdateAccessRequest","type":"object"},"OkFlag":{"properties":{"ok":{"example":true,"type":"boolean"}},"required":["ok"],"title":"OkFlag","type":"object"},"BatchCreateFoldersResponse":{"properties":{"folders":{"items":{"properties":{"count":{"type":"integer"},"input":{"description":"The folder as sent","type":"string"},"name":{"description":"The folder as stored (sanitized)","type":"string"}},"required":["input","name"],"type":"object"},"type":"array"},"rejected":{"description":"Inputs that sanitize to the vault root; nothing was created for them","items":{"type":"string"},"type":"array"}},"required":["folders","rejected"],"title":"BatchCreateFoldersResponse","type":"object"},"BatchCreateFoldersRequest":{"example":{"folders":["Projects/Engram","Archive"]},"properties":{"folders":{"items":{"type":"string"},"maxItems":500,"type":"array"}},"required":["folders"],"title":"BatchCreateFoldersRequest","type":"object"},"RenameNoteResponse":{"properties":{"new_path":{"type":"string"},"note":{"$ref":"#/components/schemas/Note"},"old_path":{"type":"string"},"renamed":{"type":"boolean"}},"title":"RenameNoteResponse","type":"object"},"Connection":{"properties":{"cimd_url":{"description":"CIMD metadata-document URL. Present only for clients that published one, in which case it is the client's public identifier and the reason it can be `verified` despite redirecting to loopback.","example":"https://claude.ai/.well-known/oauth-client","nullable":true,"type":"string"},"client_id":{"nullable":true,"type":"string"},"connected_at":{"format":"date-time","nullable":true,"type":"string"},"family_id":{"description":"The grant lineage this row is. One OAuth client can hold several grants over different vault sets, each its own row; pass this to `DELETE /connections/oauth/{client_id}` to revoke only this one. Null for PATs.","format":"uuid","nullable":true,"type":"string"},"first_ip":{"nullable":true,"type":"string"},"first_user_agent":{"nullable":true,"type":"string"},"key_id":{"nullable":true,"type":"string"},"kind":{"example":"oauth","type":"string"},"label":{"description":"The user's own name for this connection, when they set one.","nullable":true,"type":"string"},"last_used_at":{"format":"date-time","nullable":true,"type":"string"},"logo":{"nullable":true,"type":"string"},"name":{"nullable":true,"type":"string"},"new_vaults":{"description":"The level this connection gets on vaults you create later.","enum":["none","read","write"],"type":"string"},"redirect_uri":{"description":"The redirect this grant's authorization code was delivered to. This, not `redirect_uris`, is what decides `verified`. Null for non-OAuth connections and for grants issued before it was recorded.","nullable":true,"type":"string"},"redirect_uris":{"description":"Every redirect the client registered. Informational only — a client may register several and choose one per authorization, so a vendor host appearing here proves nothing about this grant.","items":{"type":"string"},"nullable":true,"type":"array"},"scope":{"nullable":true,"type":"string"},"slug":{"nullable":true,"type":"string"},"software_id":{"nullable":true,"type":"string"},"software_version":{"nullable":true,"type":"string"},"vault_ids":{"description":"Vaults this connection may reach right now (read or write). Null means every vault, including ones created later. For a connection with per-vault levels this is resolved, not stored: the vaults `vault_levels` shows at read or write. Vaults created later get `new_vaults`, so read both for the full picture.","items":{"format":"uuid","type":"string"},"nullable":true,"type":"array"},"vault_levels":{"description":"What this connection may do in each of your vaults, resolved now. Every vault you can see is listed, `none` included. `read` = search and read, `write` = also change notes and the vault.","items":{"properties":{"level":{"enum":["none","read","write"],"type":"string"},"vault_id":{"format":"uuid","type":"string"},"vault_name":{"nullable":true,"type":"string"}},"required":["vault_id","level"],"type":"object"},"type":"array"},"vault_names":{"description":"Positional against `vault_ids`. An entry is null when the vault is gone or outside the caller's own scope.","items":{"nullable":true,"type":"string"},"nullable":true,"type":"array"},"verified":{"nullable":true,"type":"boolean"}},"required":["kind"],"title":"Connection","type":"object"},"ApiKeyCreated":{"properties":{"default_access":{"description":"The granted level for every vault not in `vault_access`, including later ones.","enum":["none","read","write"],"type":"string"},"id":{"format":"uuid","type":"string"},"key":{"description":"Raw secret — store it now; never shown again.","type":"string"},"name":{"type":"string"},"vault_access":{"additionalProperties":{"enum":["none","read","write"],"type":"string"},"description":"What the key was granted, read back from storage: a level per listed vault id. Compare it with what you asked for; revoke the key if it is wider.","type":"object"}},"required":["key","id","name","vault_access","default_access"],"title":"ApiKeyCreated","type":"object"},"TagsResponse":{"properties":{"tags":{"items":{"properties":{"name":{"type":"string"}},"type":"object"},"type":"array"}},"required":["tags"],"title":"TagsResponse","type":"object"},"FolderRenameResponse":{"properties":{"count":{"type":"integer"},"new_path":{"type":"string"},"old_path":{"type":"string"},"renamed":{"type":"boolean"}},"title":"FolderRenameResponse","type":"object"},"BatchIdsRequest":{"example":{"ids":["9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"]},"properties":{"ids":{"items":{"format":"uuid","type":"string"},"type":"array"}},"required":["ids"],"title":"BatchIdsRequest","type":"object"},"VaultTreeNote":{"properties":{"created_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"path":{"type":"string"},"updated_at":{"format":"date-time","type":"string"}},"required":["id","path","created_at","updated_at"],"title":"VaultTreeNote","type":"object"},"Vault":{"properties":{"attachment_count":{"type":"integer"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"deleted_at":{"format":"date-time","nullable":true,"type":"string"},"encrypted":{"description":"Always true — vaults are encrypted at rest.","type":"boolean"},"id":{"format":"uuid","type":"string"},"is_default":{"type":"boolean"},"name":{"type":"string"},"note_count":{"type":"integer"},"populated":{"description":"True once the vault holds a note other than the seeded welcome note (the moment the `vault_populated` event fires).","type":"boolean"},"purge_at":{"description":"When a soft-deleted vault is hard-purged (deleted_at + 30d).","format":"date-time","nullable":true,"type":"string"},"slug":{"nullable":true,"type":"string"}},"required":["id","name"],"title":"Vault","type":"object"},"CreateFolderRequest":{"example":{"folder":"Projects/Engram"},"properties":{"folder":{"type":"string"}},"required":["folder"],"title":"CreateFolderRequest","type":"object"},"CreateApiKeyRequest":{"example":{"default_access":"none","name":"CI pipeline","vault_access":{"0b7c6a4e-3f1d-4c8e-9a2b-5d6e7f809a1b":"read"}},"properties":{"default_access":{"description":"Level for every vault not listed in `vault_access`, including vaults you create later. Defaults to `none` when `vault_access` is given. `write` also lets the key create vaults. The key must reach at least one vault now or later.","enum":["none","read","write"],"type":"string"},"name":{"type":"string"},"vault_access":{"additionalProperties":{"enum":["none","read","write"],"type":"string"},"description":"Level per vault id: `none`, `read` (search and read) or `write` (also create, edit, rename and delete notes, and rename or delete the vault). Every id must be one of your vaults. Omit both this and `default_access` for a key on every vault with full write access.","type":"object"}},"required":["name"],"title":"CreateApiKeyRequest","type":"object"},"RegisterVaultRequest":{"example":{"client_id":"obsidian-9f1b2c3d4e5f","name":"My Vault"},"properties":{"client_id":{"description":"Client-generated stable vault id.","type":"string"},"name":{"type":"string"}},"required":["name","client_id"],"title":"RegisterVaultRequest","type":"object"},"FolderResponse":{"properties":{"folder":{"$ref":"#/components/schemas/Folder"}},"required":["folder"],"title":"FolderResponse","type":"object"},"LogInput":{"properties":{"category":{"nullable":true,"type":"string"},"conn_id":{"description":"WebSocket connection id, correlates client logs to server breadcrumbs.","nullable":true,"type":"string"},"device_id":{"description":"Per-install Obsidian instance id, for per-device attribution.","nullable":true,"type":"string"},"forced":{"description":"Set by the client when the entry bypassed its diagnostics gate (RemoteLogger.anomaly). Provenance, not severity: it marks a signal that reaches the whole fleet rather than only opted-in users.","nullable":true,"type":"boolean"},"level":{"example":"error","type":"string"},"message":{"type":"string"},"platform":{"nullable":true,"type":"string"},"plugin_version":{"nullable":true,"type":"string"},"stack":{"nullable":true,"type":"string"},"ts":{"format":"date-time","nullable":true,"type":"string"}},"title":"LogInput","type":"object"},"LogIngestResponse":{"properties":{"count":{"description":"Number of log lines persisted.","type":"integer"},"ok":{"example":true,"type":"boolean"}},"required":["ok","count"],"title":"LogIngestResponse","type":"object"},"RenameRequest":{"example":{"new_path":"Archive/Engram.md","old_path":"Projects/Engram.md"},"properties":{"new_path":{"type":"string"},"old_path":{"type":"string"}},"required":["old_path","new_path"],"title":"RenameRequest","type":"object"},"UserResponse":{"properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"],"title":"UserResponse","type":"object"},"Backlinks":{"properties":{"backlinks":{"description":"Capped at 200 edges, ordered oldest-first. Not paginated yet.","items":{"$ref":"#/components/schemas/Backlink"},"type":"array"}},"required":["backlinks"],"title":"Backlinks","type":"object"},"SearchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/SearchResult"},"type":"array"}},"required":["results"],"title":"SearchResponse","type":"object"},"NoteMeta":{"properties":{"content_hash":{"nullable":true,"type":"string"},"folder":{"nullable":true,"type":"string"},"id":{"format":"uuid","nullable":true,"type":"string"},"mtime":{"format":"float","type":"number"},"parse_reason":{"description":"Present when parse_status is \"degraded\": {code, message, detail}","nullable":true,"type":"object"},"parse_status":{"description":"Frontmatter parse outcome","enum":["ok","degraded"],"nullable":true,"type":"string"},"path":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array"},"title":{"nullable":true,"type":"string"},"updated_at":{"format":"date-time","nullable":true,"type":"string"},"version":{"nullable":true,"type":"integer"}},"required":["path"],"title":"NoteMeta","type":"object"},"AttachmentBatchDeleteRequest":{"example":{"paths":["assets/diagram.png","assets/old-logo.svg"]},"properties":{"paths":{"items":{"type":"string"},"type":"array"}},"required":["paths"],"title":"AttachmentBatchDeleteRequest","type":"object"},"FolderListResponse":{"properties":{"folder":{"type":"string"},"notes":{"items":{"$ref":"#/components/schemas/NoteMeta"},"type":"array"}},"required":["folder","notes"],"title":"FolderListResponse","type":"object"},"LimitError":{"properties":{"current":{"nullable":true,"type":"integer"},"error":{"example":"limit_exceeded","type":"string"},"limit":{"description":"Integer or boolean cap.","nullable":true,"oneOf":[{"type":"integer"},{"type":"boolean"}]},"limit_key":{"example":"vaults_cap","nullable":true,"type":"string"},"reason":{"example":"vaults_cap_exceeded","type":"string"},"tier":{"example":"free","nullable":true,"type":"string"},"upgrade_url":{"nullable":true,"type":"string"}},"required":["error","reason"],"title":"LimitError","type":"object"},"HealthStatus":{"description":"Service health status.","example":{"build_sha":"e998801","status":"ok","version":"0.5.464"},"properties":{"build_sha":{"description":"Commit the running image was built from, baked in at build time. null on local/self-host builds where it was not supplied.","example":"e998801f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d","nullable":true,"type":"string"},"checks":{"additionalProperties":{"type":"string"},"description":"Per-dependency status map (present on /deep only)","type":"object"},"status":{"description":"ok | degraded","example":"ok","type":"string"},"version":{"description":"Running app version from mix.exs. Sticky between release-please cuts, so it does NOT change on a non-release deploy — use build_sha to tell what code is actually running.","example":"0.5.464","type":"string"}},"required":["status"],"title":"HealthStatus","type":"object"},"StorageUsage":{"properties":{"file_count":{"type":"integer"},"max_attachment_bytes":{"description":"Per-file cap for the user's plan.","type":"integer"},"max_bytes":{"type":"integer"},"used_bytes":{"type":"integer"}},"required":["used_bytes","file_count","max_bytes","max_attachment_bytes"],"title":"StorageUsage","type":"object"},"BatchMoveNotesRequest":{"example":{"ids":["9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d","1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"],"target_folder_id":"7c8d9e0f-1a2b-3c4d-5e6f-7a8b9c0d1e2f"},"properties":{"ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"target_folder":{"description":"Destination folder path (alternative to target_folder_id; \"\" = vault root). Use this to move into a derived folder that has no marker.","type":"string"},"target_folder_id":{"description":"Folder UUID or \"root\".","type":"string"}},"required":["ids"],"title":"BatchMoveNotesRequest","type":"object"},"VaultPurged":{"properties":{"id":{"format":"uuid","type":"string"},"purged":{"example":true,"type":"boolean"}},"required":["purged","id"],"title":"VaultPurged","type":"object"},"AttachmentWithContent":{"properties":{"content_base64":{"description":"Base64-encoded file bytes.","type":"string"},"content_hash":{"description":"Opaque content fingerprint — see AttachmentMeta.content_hash.","nullable":true,"type":"string"},"created_at":{"format":"date-time","nullable":true,"type":"string"},"id":{"format":"uuid","nullable":true,"type":"string"},"mime_type":{"nullable":true,"type":"string"},"mtime":{"format":"float","type":"number"},"path":{"type":"string"},"size_bytes":{"type":"integer"},"updated_at":{"format":"date-time","nullable":true,"type":"string"}},"required":["path","content_base64"],"title":"AttachmentWithContent","type":"object"},"TypesResponse":{"properties":{"types":{"items":{"properties":{"name":{"type":"string"}},"type":"object"},"type":"array"}},"required":["types"],"title":"TypesResponse","type":"object"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","description":"Sent as `Authorization: Bearer <token>` — a Clerk/local JWT, an OAuth access token, or a personal API key/PAT.","scheme":"bearer","type":"http"}}},"info":{"description":"REST API for Engram — notes, search, sync, billing, and MCP.","title":"Engram API","version":"1.0.0"},"openapi":"3.0.0","paths":{"/api/attachments/changes":{"get":{"callbacks":{},"deprecated":true,"description":"Retired. The timestamp-based attachment change feed has been removed; this endpoint always returns 410 Gone. Clients sync via the CRDT sync socket and `GET /sync/manifest` (current plugin versions already do).","operationId":"attachments-changes","parameters":[],"responses":{"410":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Feed retired"}},"summary":"Retired timestamp change feed","tags":["Attachments"]}},"/api/attachments/batch-delete":{"post":{"callbacks":{},"description":"Deletes multiple attachments by path and returns the deleted count. Deliberately not billing-gated so a downgraded user can always clean up. Requires the `X-Idempotency-Key` header for safe retries.","operationId":"attachments-batch-delete","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentBatchDeleteRequest"}}},"description":"Paths","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedCount"}}},"description":"Deleted count"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Missing paths"}},"summary":"Delete attachments by path (idempotent)","tags":["Attachments"]}},"/api/attachments/*path":{"delete":{"callbacks":{},"description":"Deletes the attachment at the given path. Idempotent — always returns `deleted: true` with the path, and is not billing-gated so downgraded users can still remove files.","operationId":"attachments-delete","parameters":[{"description":"Attachment path","in":"path","name":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentDeleted"}}},"description":"Deleted"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Delete failed"}},"summary":"Delete an attachment","tags":["Attachments"]},"get":{"callbacks":{},"description":"Returns metadata + base64 content by default. Pass `?raw=1` to stream the raw bytes instead; the metadata then rides in `x-engram-content-hash`, `x-engram-mime-type`, `x-engram-mtime` and `x-engram-updated-at` response headers.","operationId":"attachments-show","parameters":[{"description":"Attachment path","in":"path","name":"path","required":true,"schema":{"type":"string"}},{"description":"\"1\" streams raw bytes instead of JSON.","in":"query","name":"raw","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentWithContent"}}},"description":"Attachment"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such attachment"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Fetch error"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Storage fetch failed"}},"summary":"Get an attachment","tags":["Attachments"]}},"/api/notes/append":{"post":{"callbacks":{},"description":"Appends `text` to the note at `path`, returning `created: false`. If the note does not exist it is created with a heading derived from the filename plus the text, returning `created: true`.","operationId":"notes-append","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppendRequest"}}},"description":"Path + text","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppendResponse"}}},"description":"Appended"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Append text to a note (creating it if absent)","tags":["Notes"]}},"/api/folders/batch":{"post":{"callbacks":{},"description":"Creates explicit folder markers for up to 500 folders in one transaction and returns 201 with one entry per distinct input: the `input` as sent and the `name` it was stored under. Idempotent per folder like `POST /folders`. Paths are sanitized (traversal segments and illegal characters stripped); inputs that sanitize to the root are listed in `rejected` and create nothing. More than 500 entries is rejected with 422.","operationId":"folders-batch-create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateFoldersRequest"}}},"description":"Folder paths","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateFoldersResponse"}}},"description":"Created"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid input or too many folders"}},"summary":"Create many folders","tags":["Folders"]}},"/api/notes/rename":{"post":{"callbacks":{},"description":"Moves the note from `old_path` to `new_path` and returns the updated note. Returns 404 when the source note is missing and 409 when the target path already exists or the note version conflicts.","operationId":"notes-rename","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameRequest"}}},"description":"Old + new path","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameNoteResponse"}}},"description":"Renamed"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such note"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Target exists / version conflict"}},"summary":"Rename / move a note","tags":["Notes"]}},"/api/user/storage":{"get":{"callbacks":{},"description":"Returns the user's current attachment storage usage and file count alongside the total storage cap and the per-file size limit resolved from their plan. On self-host (limits not enforced) the per-file cap is reported as the Pro ceiling.","operationId":"account-storage","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageUsage"}}},"description":"Storage usage"}},"summary":"Get storage usage and caps","tags":["Account"]}},"/api/connections/oauth/{client_id}":{"delete":{"callbacks":{},"description":"Revokes the refresh tokens for an OAuth client family (e.g. an MCP client) so it can no longer mint access tokens. Pass `family_id` to revoke ONE grant — a client the user authorized twice has two, and each is its own row in `GET /connections`. Omit it to disconnect the client entirely, which is what frees a connection-cap slot. `vault_id` is the older, coarser narrowing and is ignored when `family_id` is given: it revokes, whole, each of the client's grants that can reach that vault. A grant with per-vault levels counts as reaching it at read or write, and through its new-vaults policy; a grant that lists vaults without levels, when the vault is one of them; and an all-vaults grant, which reaches every vault. A revoked grant's access tokens stop working at once and its live sockets are disconnected. Session-auth only.","operationId":"connections-delete-oauth","parameters":[{"description":"OAuth client id","in":"path","name":"client_id","required":true,"schema":{"type":"string"}},{"description":"Revoke only this grant lineage","in":"query","name":"family_id","required":false,"schema":{"type":"string"}},{"description":"Revoke only the grants that can reach this vault, all-vaults grants included","in":"query","name":"vault_id","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked (empty body)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such connection"}},"summary":"Revoke an OAuth client connection","tags":["Connections"]},"patch":{"callbacks":{},"description":"Replaces the vault levels of ONE grant (`family_id`) of an OAuth client. Takes effect at once: issued access tokens and every later refresh carry the new access, and the user's live sockets reconnect. Widening is allowed. A grant the client asked to be read only (`mcp:read`) cannot be given write: 422. Session-auth only.","operationId":"connections-update-oauth","parameters":[{"description":"OAuth client id","in":"path","name":"client_id","required":true,"schema":{"type":"string"}},{"description":"The grant to change","in":"query","name":"family_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAccessRequest"}}},"description":"New access","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Connection"}}},"description":"The updated connection"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Missing family_id"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such connection"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Access the grant cannot have"}},"summary":"Change an OAuth connection's vault access","tags":["Connections"]}},"/api/embed-status":{"get":{"callbacks":{},"operationId":"embed-status","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedStatusResponse"}}},"description":"Index status"}},"summary":"Get embedding/index progress","tags":["Embedding"],"x-internal":true}},"/api/health/deep":{"get":{"callbacks":{},"description":"Checks dependencies whose absence fails every request (Postgres), plus BEAM cluster join on clustered deploys. 503 when degraded.","operationId":"health-deep","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}},"description":"All critical deps ok"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}},"description":"Degraded"}},"security":[],"summary":"Readiness probe","tags":[]}},"/api/connections/device/{family_id}":{"delete":{"callbacks":{},"description":"Revokes a linked device family (e.g. an Obsidian plugin install) by its family id, invalidating its refresh tokens. Session-auth only.","operationId":"connections-delete-device","parameters":[{"description":"Device family id","in":"path","name":"family_id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked (empty body)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such connection"}},"summary":"Revoke a device connection","tags":["Connections"]}},"/api/types":{"get":{"callbacks":{},"description":"Returns the distinct set of Open Knowledge Format `type` values used across all notes in the current vault, normalised (NFKC + lowercase) to match how the search filter buckets them.","operationId":"types","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TypesResponse"}}},"description":"Types"}},"summary":"List all OKF types in the vault","tags":["Types"]}},"/api/vaults":{"get":{"callbacks":{},"description":"Lists the user's active vaults with their note and attachment counts. Pass `deleted=true` to list soft-deleted vaults instead, or `user_code` to also report `user_code_valid` and a `suggested_vault_name` for an in-progress device link.","operationId":"vaults-index","parameters":[{"description":"Pass \"true\" to list soft-deleted vaults instead of active ones.","in":"query","name":"deleted","required":false,"schema":{"type":"string"}},{"description":"Device-link user code; echoes a `suggested_vault_name`.","in":"query","name":"user_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultsResponse"}}},"description":"Vaults"}},"summary":"List vaults","tags":["Vaults"]}},"/api/notes/*path":{"delete":{"callbacks":{},"description":"Deletes the note at the given path. Idempotent — deleting a non-existent note still returns `deleted: true`.","operationId":"notes-delete","parameters":[{"description":"Note path","in":"path","name":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedFlag"}}},"description":"Deleted"}},"summary":"Delete a note by path","tags":["Notes"]},"get":{"callbacks":{},"description":"Returns the full note (including content) at the given slash-separated path, or 404 if no note exists there.","operationId":"notes-show","parameters":[{"description":"Note path (slash-separated)","in":"path","name":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}},"description":"Note"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such note"}},"summary":"Get a note by path","tags":["Notes"]}},"/api/tags":{"get":{"callbacks":{},"description":"Returns the distinct set of tags used across all notes in the current vault.","operationId":"tags","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagsResponse"}}},"description":"Tags"}},"summary":"List all tags in the vault","tags":["Tags"]}},"/api/logs":{"get":{"callbacks":{},"operationId":"logs-list","parameters":[{"description":"Filter by level","in":"query","name":"level","required":false,"schema":{"type":"string"}},{"description":"Filter by category","in":"query","name":"category","required":false,"schema":{"type":"string"}},{"description":"ISO 8601 lower bound","in":"query","name":"since","required":false,"schema":{"type":"string"}},{"description":"Max rows","in":"query","name":"limit","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogsResponse"}}},"description":"Logs"}},"summary":"List ingested logs","tags":["Logs"],"x-internal":true},"post":{"callbacks":{},"operationId":"logs-ingest","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogIngestRequest"}}},"description":"Log lines","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogIngestResponse"}}},"description":"Persisted count"}},"summary":"Ingest client logs","tags":["Logs"],"x-internal":true}},"/api/attachments/batch-move":{"post":{"callbacks":{},"description":"Moves multiple attachments (by path) into `target_folder` and returns the moved count. Requires a plan with attachments enabled (402 otherwise) and the `X-Idempotency-Key` header. Returns 404/409 (with the offending `item_path`) if an item is missing or conflicts at the target.","operationId":"attachments-batch-move","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentBatchMoveRequest"}}},"description":"Paths + target folder","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovedCount"}}},"description":"Moved count"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Missing params"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}},"description":"Attachments require a paid plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchItemError"}}},"description":"An item was not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchItemError"}}},"description":"An item conflicts at target"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Target path cleans to nothing"}},"summary":"Move attachments to a folder (idempotent)","tags":["Attachments"]}},"/api/folders":{"get":{"callbacks":{},"description":"Returns every folder in the current vault with its note count, plus a stable marker id and parent id so clients can rebuild the folder hierarchy. Derived (no-marker) folders and the root return a null id and are not eligible as parents.","operationId":"folders-index","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoldersResponse"}}},"description":"Folders"}},"summary":"List folders","tags":["Folders"]},"post":{"callbacks":{},"description":"Creates an explicit folder marker so the folder can exist while empty. Returns 201 with the folder (count 0). An empty or root folder path is rejected with 422.","operationId":"folders-create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFolderRequest"}}},"description":"Folder path","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderResponse"}}},"description":"Created"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Empty/root folder rejected"}},"summary":"Create a folder","tags":["Folders"]}},"/api/connections/pat":{"post":{"callbacks":{},"description":"The raw `key` is returned once. Any plan can create a key, and it works on every route (REST, MCP, sync sockets) on every plan, rate limited per user (`api_rps_cap`).","operationId":"connections-create-pat","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePatRequest"}}},"description":"PAT name","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}},"description":"Created (raw key)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Blank/invalid name"}},"summary":"Create a personal access token","tags":["Connections"]}},"/api/api-keys/{id}":{"delete":{"callbacks":{},"description":"Permanently revokes the API key with the given UUID so it can no longer authenticate. Returns 400 for a malformed id and 404 when no such key belongs to the user.","operationId":"apikeys-revoke","parameters":[{"description":"API key UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedFlag"}}},"description":"Revoked"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Invalid id"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such key"}},"summary":"Revoke an API key","tags":["API Keys"]}},"/api/attachments/rename":{"post":{"callbacks":{},"description":"Moves the attachment from `old_path` to `new_path`. Requires a plan with attachments enabled (402 otherwise). Returns 404 when the source is missing and 409 when the target path already exists.","operationId":"attachments-rename","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameRequest"}}},"description":"Old + new path","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentRenameResponse"}}},"description":"Renamed"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}},"description":"Attachments require a paid plan"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such attachment"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Target exists"}},"summary":"Rename / move an attachment","tags":["Attachments"]}},"/api/me":{"delete":{"callbacks":{},"description":"Irreversible. Requires the account password for confirmation.","operationId":"account-delete","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccountRequest"}}},"description":"Password confirmation","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkFlag"}}},"description":"Account deleted"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"password_required"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"invalid_password"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"last_admin — cannot delete the only admin"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"delete_failed"}},"summary":"Delete the current user's account","tags":["Account"]},"get":{"callbacks":{},"description":"Returns the authenticated user's id, email, role, display name, and UI language.","operationId":"account-me","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}},"description":"Current user"}},"summary":"Get the current user","tags":["Account"]},"patch":{"callbacks":{},"description":"Updates the authenticated user's mutable profile fields (`display_name`, `locale`, `analytics_opt_out`) and returns the updated user.","operationId":"account-update","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileRequest"}}},"description":"Profile fields","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}},"description":"Updated user"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Update the current user's profile","tags":["Account"]}},"/api/sync/manifest":{"get":{"callbacks":{},"description":"Every live note + attachment path, content hash, and change seq, for sync reconciliation. Pass `since_seq` (the `change_seq` of a previous manifest) to short-circuit: when nothing has changed the response is just `{unchanged: true, change_seq}` with the body omitted.","operationId":"sync-manifest","parameters":[{"description":"Watermark from a prior manifest's `change_seq`; invalid values are ignored.","example":"1042","in":"query","name":"since_seq","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}},"description":"Manifest"}},"summary":"Get the full vault manifest","tags":["Sync"]}},"/api/notes":{"post":{"callbacks":{},"description":"Creates a note or updates the existing one at the same path. Updates are merged convergently (CRDT) with the stored content. Optionally pass `base_hash` — the `content_hash` you last read — for compare-and-swap semantics: if the note changed since that read, the write returns 409 with the current server note instead of merging. Notes over 10MB are rejected with 413, and exceeding the plan's note cap returns 402.","operationId":"notes-upsert","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertNoteRequest"}}},"description":"Note to upsert","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteResponse"}}},"description":"Created/updated"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}},"description":"Version conflict"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Note exceeds 10MB"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Create or update a note","tags":["Notes"]}},"/api/notes/changes":{"get":{"callbacks":{},"deprecated":true,"description":"Retired. The timestamp-based change feed has been removed; this endpoint always returns 410 Gone. Clients sync via the CRDT sync socket and `GET /sync/manifest` (current plugin versions already do).","operationId":"notes-changes","parameters":[],"responses":{"410":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Feed retired"}},"summary":"Retired timestamp change feed","tags":["Notes"]}},"/api/folders/rename":{"post":{"callbacks":{},"description":"Renames or moves a folder from `old_path` to `new_path`, re-homing every note beneath it, and returns the affected note count. Returns 404 when the source folder does not exist and 409 when the target path is already occupied.","operationId":"folders-rename","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameRequest"}}},"description":"Old + new path","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderRenameResponse"}}},"description":"Renamed"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such folder"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Target exists"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Target path cleans to nothing"}},"summary":"Rename / move a folder","tags":["Folders"]}},"/api/vaults/register":{"post":{"callbacks":{},"description":"Used by the plugin on first sync. Returns 201 with `status: created` for a new vault, or 200 with `status: existing` when the client_id already maps to one.","operationId":"vaults-register","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterVaultRequest"}}},"description":"Name + client_id","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterVaultResponse"}}},"description":"Existing vault"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterVaultResponse"}}},"description":"Newly created vault"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"name and client_id are required"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}},"description":"Vault cap reached"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Vault outside the credential's scope, or creation by a credential whose new-vaults policy is not write (a vault-restricted, read-level or `mcp:read` credential)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Register or fetch a vault by client_id (idempotent)","tags":["Vaults"]}},"/api/connections/pat/{id}":{"delete":{"callbacks":{},"description":"Revokes the personal access token (API key) with the given UUID so it can no longer authenticate. Returns 404 for an unknown or malformed id. Session-auth only.","operationId":"connections-delete-pat","parameters":[{"description":"PAT (API key) UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked (empty body)"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such PAT"}},"summary":"Revoke a personal access token","tags":["Connections"]},"patch":{"callbacks":{},"description":"Replaces the vault levels of a key created with levels (`engram_lk_`). The raw key is unchanged, and the change takes effect at once. A key created before levels cannot be changed (422); create a new key instead. 404 for an unknown or malformed id. Session-auth only.","operationId":"connections-update-pat","parameters":[{"description":"PAT (API key) UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAccessRequest"}}},"description":"New access","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Connection"}}},"description":"The updated connection"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such PAT"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Access the key cannot have"}},"summary":"Change a personal access token's vault access","tags":["Connections"]}},"/api/folders/batch-move":{"post":{"callbacks":{},"description":"Re-parents multiple folders under `target_parent_id` (or the literal `\"root\"` for top level) in one transaction and returns the moved count. Requires the `X-Idempotency-Key` header. Returns 404 for a missing id and 409 (with `item_id`) on a conflict or a cycle.","operationId":"folders-batch-move","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMoveFoldersRequest"}}},"description":"Ids + target parent","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovedCount"}}},"description":"Moved count"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid input"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Some ids not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conflict"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Target path cleans to nothing"}},"summary":"Move folders under a new parent (idempotent)","tags":["Folders"]}},"/api/attachments":{"get":{"callbacks":{},"description":"Returns metadata (path, MIME type, size, timestamps — no bytes) for every attachment in the current vault. Returns 500 if the listing fails.","operationId":"attachments-index","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentsResponse"}}},"description":"Attachments"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Listing failed"}},"summary":"List attachments (metadata only)","tags":["Attachments"]},"post":{"callbacks":{},"description":"Uploads an attachment to the vault's storage backend. Send the raw bytes as `application/octet-stream` with `path`, `mtime` and optional `mime_type` in the query string, or (legacy) base64 JSON. Attachments are a paid-tier feature (402 on Free, which is also limited to text MIME types), the MIME type and extension must pass the whitelist (415), the file must be within the per-plan size and total-quota limits (402), a raw body over the request ceiling returns 413, a raw body that stalls returns 408, a non-numeric `mtime` or `mime_type` returns 422, and a storage backend failure returns 502.","operationId":"attachments-upload","parameters":[{"description":"Vault-relative path. Required for an octet-stream body.","in":"query","name":"path","required":false,"schema":{"type":"string"}},{"description":"File mtime in seconds (octet-stream body).","in":"query","name":"mtime","required":false,"schema":{"type":"number"}},{"description":"MIME override; detected from the extension when absent (octet-stream body).","in":"query","name":"mime_type","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadAttachmentRequest"}},"application/octet-stream":{"schema":{"format":"binary","type":"string"}}},"description":"Attachment bytes","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentResponse"}}},"description":"Uploaded"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Invalid base64"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}},"description":"Attachments require a paid plan / quota"},"408":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Raw body stalled before it finished"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Raw body over the request ceiling"},"415":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MimeRejected"}}},"description":"MIME or extension not allowed"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Missing/invalid content"},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Storage backend failure"}},"summary":"Upload an attachment","tags":["Attachments"]}},"/api/notes/by-id/{id}/backlinks":{"get":{"callbacks":{},"description":"Returns every note that links to the given note UUID (the inverse of a note's own `links`). Returns 400 for a malformed UUID and 404 when no such note exists in the vault.","operationId":"notes-backlinks","parameters":[{"description":"Note UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Backlinks"}}},"description":"Backlinks"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid UUID"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such note"}},"summary":"Get backlinks for a note","tags":["Notes"]}},"/api/notes/batch-move":{"post":{"callbacks":{},"description":"Moves multiple notes into the folder identified by `target_folder_id` (or the literal `\"root\"` for the vault root) in one transaction and returns the moved count. Requires the `X-Idempotency-Key` header. Returns 404/409 (with `item_id`) if any id is missing or conflicts.","operationId":"notes-batch-move","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMoveNotesRequest"}}},"description":"Ids + target folder","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MovedCount"}}},"description":"Moved count"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid input"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Some ids not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conflict"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Target path cleans to nothing"}},"summary":"Move notes to a folder (idempotent)","tags":["Notes"]}},"/api/connections":{"get":{"callbacks":{},"description":"OAuth client families, device families, and PATs. Session-auth only.","operationId":"connections-list","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionsList"}}},"description":"Connections"}},"summary":"List active connections","tags":["Connections"]}},"/api/folders/*path":{"delete":{"callbacks":{},"description":"Removes the folder marker for the given path. Idempotent — deleting a non-existent or never-encrypted folder still returns 204. Pass `recursive=true` to also delete every note and attachment under the path — required to remove a folder that has no marker of its own (one the server only derives from the paths of the notes inside it).","operationId":"folders-delete","parameters":[{"description":"Folder path","in":"path","name":"path","required":true,"schema":{"type":"string"}},{"description":"\"true\" also deletes every note and attachment under the path. Omit for marker-only. Declared as a string, not a boolean, because nothing casts query params here — a value this does not recognize would silently no-op a destructive call.","in":"query","name":"recursive","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted (empty body)"}},"summary":"Delete a folder by path","tags":["Folders"]}},"/api/folders/list":{"get":{"callbacks":{},"description":"Returns metadata (no content) for the notes in the folder named by the required `folder` query param. Returns 400 when the param is missing.","operationId":"folders-list","parameters":[{"description":"Folder path","in":"query","name":"folder","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderListResponse"}}},"description":"Folder notes"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing folder param"}},"summary":"List notes in a folder (metadata only)","tags":["Folders"]}},"/api/search":{"post":{"callbacks":{},"description":"Searches the current vault and returns one result per matching note (chunk hits are grouped by note, ranked by best chunk score). `mode` selects vector, keyword, or hybrid (default) retrieval, and results may be narrowed by `tags` or `folder`. Setting `cross_vault` searches across all of the user's vaults and requires the Pro plan (403 otherwise).","operationId":"search","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}},"description":"Search query","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}},"description":"Results"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"cross_vault requires Pro"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing query"}},"summary":"Search notes (vector / keyword / hybrid)","tags":["Search"]}},"/api/vaults/{id}/restore":{"post":{"callbacks":{},"description":"Restores a soft-deleted vault back to active within its 30-day window. Returns 402 when restoring would exceed the plan's vault cap and 404 when no such vault exists.","operationId":"vaults-restore","parameters":[{"description":"Vault UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultResponse"}}},"description":"Restored"},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitError"}}},"description":"Vault cap reached"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"The credential may not restore this vault: this needs write access to it"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such vault, or one the credential cannot reach (the two look the same)"}},"summary":"Restore a soft-deleted vault","tags":["Vaults"]}},"/api/vaults/{id}/purge":{"post":{"callbacks":{},"description":"Immediate, irreversible hard delete — skips the 30-day soft-delete window.","operationId":"vaults-purge","parameters":[{"description":"Vault UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultPurged"}}},"description":"Purged"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such vault"}},"summary":"Permanently purge a vault","tags":["Vaults"]}},"/api/notes/batch-delete":{"post":{"callbacks":{},"description":"Deletes multiple notes by id in a single transaction and returns the deleted count. Requires the `X-Idempotency-Key` header for safe retries. Returns 404/409 (with the offending `item_id`) if any id is missing or conflicts.","operationId":"notes-batch-delete","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchIdsRequest"}}},"description":"Note ids","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedCount"}}},"description":"Deleted count"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid ids"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Some ids not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conflict"}},"summary":"Delete notes by id (idempotent)","tags":["Notes"]}},"/api/folders/by-id/{id}/notes":{"get":{"callbacks":{},"description":"Returns metadata (no content) for the notes in the folder identified by its marker UUID. Returns 400 for a malformed UUID and 404 when no such folder exists.","operationId":"folders-list-notes","parameters":[{"description":"Folder UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderNotesResponse"}}},"description":"Folder notes"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid UUID"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such folder"}},"summary":"List notes in a folder by id (metadata only)","tags":["Folders"]}},"/api/folders/explicit":{"get":{"callbacks":{},"description":"Returns the names of folders that were created explicitly (via a folder marker) and can therefore exist while empty, as opposed to folders merely derived from note paths.","operationId":"folders-explicit","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FolderNamesResponse"}}},"description":"Folder names"}},"summary":"List explicitly-created (empty-capable) folders","tags":["Folders"]}},"/api/api-keys":{"get":{"callbacks":{},"description":"Lists the authenticated user's API keys as metadata only (id, name, created and last-used timestamps). The raw key secret is never returned after creation.","operationId":"apikeys-list","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeysResponse"}}},"description":"API keys"}},"summary":"List API keys","tags":["API Keys"]},"post":{"callbacks":{},"description":"The raw `key` is returned once in this response and never again.","operationId":"apikeys-create","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}},"description":"Key name","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}},"description":"Created (raw key)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Create an API key","tags":["API Keys"]}},"/api/notes/by-id/{id}":{"delete":{"callbacks":{},"description":"Deletes the note with the given UUID. Returns 400 for a malformed UUID and 404 when no such note exists.","operationId":"notes-delete-by-id","parameters":[{"description":"Note UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedFlag"}}},"description":"Deleted"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid UUID"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such note"}},"summary":"Delete a note by id","tags":["Notes"]},"get":{"callbacks":{},"description":"Returns the full note (including content) for the given note UUID. Returns 400 for a malformed UUID and 404 when no such note exists in the vault.","operationId":"notes-show-by-id","parameters":[{"description":"Note UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}},"description":"Note"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid UUID"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such note"}},"summary":"Get a note by id","tags":["Notes"]}},"/api/vaults/{id}":{"delete":{"callbacks":{},"description":"Soft-deletes the vault, keeping it restorable for 30 days before it is permanently purged. Returns 404 when no such vault exists.","operationId":"vaults-delete","parameters":[{"description":"Vault UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultDeleted"}}},"description":"Soft-deleted (restorable for 30 days)"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Vault only readable by the credential: this needs write access to the vault"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such vault, or one the credential cannot reach (the two look the same)"}},"summary":"Soft-delete a vault","tags":["Vaults"]},"get":{"callbacks":{},"description":"Returns the vault with the given UUID along with its current note and attachment counts, or 404 if it does not exist or belong to the user. Opening a vault also emits a `vault_opened` analytics event.","operationId":"vaults-show","parameters":[{"description":"Vault UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultResponse"}}},"description":"Vault"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such vault"}},"summary":"Get a vault by id","tags":["Vaults"]},"patch":{"callbacks":{},"description":"Updates a vault's `name` or `is_default` flag and returns the updated vault. Returns 404 when no such vault exists and 422 on validation errors.","operationId":"vaults-update","parameters":[{"description":"Vault UUID","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVaultRequest"}}},"description":"Fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultResponse"}}},"description":"Updated"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"Vault only readable by the credential: this needs write access to the vault"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageError"}}},"description":"No such vault, or one the credential cannot reach (the two look the same)"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation error"}},"summary":"Update a vault","tags":["Vaults"]}},"/api/vault/tree":{"get":{"callbacks":{},"description":"Every folder, note and attachment the file tree renders, in one response.","operationId":"vault-tree","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VaultTreeResponse"}}},"description":"Vault tree"}},"summary":"Get the whole vault tree in one read","tags":["Vaults"]}},"/api/folders/batch-delete":{"post":{"callbacks":{},"description":"Deletes multiple folders by marker id in a single transaction and returns the deleted count. Requires the `X-Idempotency-Key` header — a retry within the TTL replays the cached result. Returns 404/409 (with the offending `item_id`) if any id is missing or conflicts.","operationId":"folders-batch-delete","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchIdsRequest"}}},"description":"Folder ids","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedCount"}}},"description":"Deleted count"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid ids"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Some ids not found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Conflict"}},"summary":"Delete folders by id (idempotent)","tags":["Folders"]}},"/api/health":{"get":{"callbacks":{},"description":"Cheap liveness check. Always 200 if the app is up.","operationId":"health","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthStatus"}}},"description":"Service is up"}},"security":[],"summary":"Liveness probe","tags":[]}}},"security":[{"bearerAuth":[]}],"servers":[{"description":"Production","url":"https://api.engram.page","variables":{}}],"tags":[{"description":"Create, read, update, delete, and sync notes.","name":"Notes"},{"description":"Browse and manage folders.","name":"Folders"},{"description":"Vector, keyword, and hybrid search.","name":"Search"},{"description":"List vault tags.","name":"Tags"},{"description":"Create, manage, soft-delete, restore, and purge vaults.","name":"Vaults"},{"description":"Current user profile, storage usage, and account deletion.","name":"Account"},{"description":"Upload, fetch, move, and delete binary attachments.","name":"Attachments"},{"description":"Vault manifest and the unified change feed.","name":"Sync"},{"description":"Embedding/index progress.","name":"Embedding"},{"description":"Client remote-log ingestion and retrieval.","name":"Logs"},{"description":"Create, list, and revoke personal API keys.","name":"API Keys"},{"description":"List and revoke OAuth clients, devices, and PATs.","name":"Connections"}]}