{"openapi":"3.0.3","info":{"title":"Memos Journal API","version":"1.0.0","description":"REST API for Memos — a self-hosted personal journaling application.\n\nThis spec covers the `/api/v1` endpoints actively used by the Haiven stack:\nmemo CRUD operations and authentication scheme. Additional endpoints exist in\nthe upstream Memos API (tags, resources, webhooks, system config) but are not\ndocumented here because they are not used in the current Haiven integration.\n\n## Authentication\n\nAll endpoints require a Bearer token generated in the Memos UI:\n**Settings → Access Tokens → Generate new token**\n\nThe token is a JWT-style opaque string. Pass it as:\n```\nAuthorization: Bearer <token>\n```\n\nThe token used by Home Assistant is stored in\n`homeassistant/config/secrets.yaml` as `memos_api_token_bearer`.\n\n## Visibility Values\n\n- `PRIVATE` — Visible only to the creator (recommended default)\n- `PROTECTED` — Visible to authenticated users\n- `PUBLIC` — Visible to anyone with the link\n\n## Content Format\n\nMemo content is Markdown. Hashtags (`#tag`) anywhere in the body are\nauto-parsed into the memo's tag list. No additional metadata fields are\nrequired — title and tags are inferred from content.\n","contact":{"name":"Haiven Infrastructure","url":"https://journal.haiven.site"},"license":{"name":"Internal Use Only"}},"servers":[{"url":"https://journal.haiven.site","description":"Production (via Traefik)"},{"url":"http://10.0.0.42:5230","description":"Direct (backend network, no TLS)"}],"tags":[{"name":"Health","description":"Service health probe"},{"name":"Memos","description":"Create, list, retrieve, and delete journal entries"}],"paths":{"/healthz":{"get":{"tags":["Health"],"summary":"Health check","description":"Returns `OK` (plain text, HTTP 200) when the service is healthy.\n\nNote: the image ships BusyBox wget only (no curl). The container\nhealthcheck uses `wget --spider http://localhost:5230/healthz`.\n","operationId":"getHealth","security":[],"responses":{"200":{"description":"Service is healthy","content":{"text/plain":{"schema":{"type":"string","example":"OK"}}}}}}},"/api/v1/memos":{"get":{"tags":["Memos"],"summary":"List memos","description":"Returns a paginated list of memos visible to the authenticated user.\nResults are in reverse-chronological order (newest first).\n","operationId":"listMemos","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","description":"Maximum number of memos to return","required":false,"schema":{"type":"integer","default":10,"minimum":1,"maximum":100},"example":10},{"name":"offset","in":"query","description":"Number of memos to skip (for pagination)","required":false,"schema":{"type":"integer","default":0,"minimum":0},"example":0}],"responses":{"200":{"description":"List of memos","content":{"application/json":{"schema":{"type":"object","properties":{"memos":{"type":"array","items":{"$ref":"#/components/schemas/Memo"}},"nextPageToken":{"type":"string","description":"Token for the next page (omitted if no further pages)"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Memos"],"summary":"Create a memo","description":"Creates a new journal entry. Content is Markdown; `#hashtags` in the\nbody are automatically parsed into tags.\n\nThe Home Assistant `memos_create_entry` REST command calls this endpoint\non the backend network (`http://10.0.0.42:5230/api/v1/memos`).\n","operationId":"createMemo","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMemoRequest"},"examples":{"voice_entry":{"summary":"Voice-dictated entry (HA pipeline format)","value":{"content":"# afternoon walk\n\nWent for a walk today. Weather was great.\n\n#walk #journal #voice","visibility":"PRIVATE"}},"simple_entry":{"summary":"Simple note","value":{"content":"Quick note — remember to check the backup logs #ops","visibility":"PRIVATE"}}}}}},"responses":{"200":{"description":"Memo created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Memo"}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/memos/{name}":{"get":{"tags":["Memos"],"summary":"Get a memo","description":"Retrieves a single memo by its resource name.\n\nThe `name` path parameter uses the format `memos/{id}` (e.g., `memos/42`).\nWhen constructing the URL, URL-encode the slash: `memos%2F42`.\n","operationId":"getMemo","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/MemoName"}],"responses":{"200":{"description":"Memo found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Memo"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Memo not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Memos"],"summary":"Delete a memo","description":"Permanently deletes a memo. This action cannot be undone.\n\nUse the `name` field from the create or list response.\n","operationId":"deleteMemo","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/MemoName"}],"responses":{"200":{"description":"Memo deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Memo not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Access token generated in Memos UI: Settings → Access Tokens → Generate new token.\n\nFor Home Assistant integration, the full `Bearer <token>` string is stored in\n`homeassistant/config/secrets.yaml` as `memos_api_token_bearer`.\n"}},"parameters":{"MemoName":{"name":"name","in":"path","required":true,"description":"Resource name in the format `memos/{id}`.\nURL-encode the slash when constructing the URL: `memos%2F42`.\n","schema":{"type":"string","pattern":"^memos/\\d+$"},"example":"memos/42"}},"schemas":{"Memo":{"type":"object","description":"A journal entry","properties":{"name":{"type":"string","description":"Resource name (e.g., `memos/42`). Use this as the path parameter for get/delete.","example":"memos/42"},"uid":{"type":"string","description":"Unique identifier (UUID)","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"content":{"type":"string","description":"Memo content in Markdown format","example":"# afternoon walk\n\nWent for a walk today. #walk #journal"},"visibility":{"type":"string","enum":["PRIVATE","PROTECTED","PUBLIC"],"description":"Entry visibility","example":"PRIVATE"},"tags":{"type":"array","items":{"type":"string"},"description":"Tags auto-parsed from `#hashtag` patterns in the content","example":["walk","journal"]},"createTime":{"type":"string","format":"date-time","description":"Creation timestamp (RFC 3339)","example":"2026-06-06T14:30:00Z"},"updateTime":{"type":"string","format":"date-time","description":"Last modification timestamp (RFC 3339)","example":"2026-06-06T14:30:00Z"},"creator":{"type":"string","description":"Creator resource name","example":"users/1"},"rowStatus":{"type":"string","description":"Record status","example":"NORMAL"}}},"CreateMemoRequest":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Memo content in Markdown format. `#hashtag` patterns are auto-parsed\ninto tags. There is no separate title field — derive a title from the\nfirst `# Heading` in the content, or leave unstructured.\n","example":"# My Entry\n\nSome journaled thoughts. #journal #daily"},"visibility":{"type":"string","enum":["PRIVATE","PROTECTED","PUBLIC"],"description":"Entry visibility. Defaults to the system-level default (PRIVATE recommended).","default":"PRIVATE"}}},"Error":{"type":"object","properties":{"code":{"type":"integer","description":"HTTP status code"},"message":{"type":"string","description":"Error description"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"code":401,"message":"unauthorized: missing or invalid token"}}}}}}}