Welcome to MiloNotes

MiloNotes is a privacy-first Android note-taking app with on-device AI, Kanban boards, semantic search, and a full REST API. Your data never leaves your device.

💡
New here? Start with Installation, then First Launch to get oriented.

What MiloNotes does

📚
Notebook hierarchy — Notebooks → Sections → Sub-Sections → Notes, with a collapsible file tree
✍️
Markdown editor — Mermaid diagrams, tables, Wikilinks, syntax highlighting, live preview
🗂️
Kanban boards — drag-and-drop tasks linked to notes
🧠
On-device AI — Gemini Nano (Pixel 9 / Galaxy S25), semantic search with RAG, memory system
🌐
Access everywhere — LAN server, internet tunnel, Cloudflare BYOK, installable PWA
🔌
REST API & MCP — full programmatic access with API key auth

Requirements

RequirementMinimumNotes
Android8.0 (API 26)All features except on-device AI
On-device AIAndroid 15 (API 35)Pixel 9 / Galaxy S25 class devices only
Storage~100 MBMore for attachments and AI models

Installation

MiloNotes is available on the Google Play Store. iOS support is coming soon.

Google Play

Search for MiloNotes on the Google Play Store, or use the link on the homepage.

⚠️
MiloNotes requires Android 8.0 or later. On-device AI features require Android 15 and a supported device (Pixel 9 series or Samsung Galaxy S25 series).

Permissions

PermissionUsed for
InternetTunnel connection, external AI providers
MicrophoneVoice transcription (optional)
Foreground ServiceLAN web server, tunnel connection
NotificationsServer status, sync progress

First Launch

When you open MiloNotes for the first time, you'll be taken through a short onboarding flow.

Onboarding

The 5-page onboarding explains the core concepts — notebooks, AI features, LAN/tunnel access, and the API. You can skip it at any time and revisit settings later.

Day View (home screen)

After onboarding, MiloNotes opens to the Day View — a calendar-integrated planner showing today's entries, your note activity, and an AI-generated daily summary.

Bottom navigation

TabDescription
📅 Day ViewDaily planner, calendar, AI summary
📚 NotebooksBook-cover grid; tap to open a notebook's file tree
🦊 MiloAI chat — ask questions grounded in your notes
🗂️ KanbanTask boards linked to notes
🔍 SearchFull-text and semantic search
⚙️ SettingsAI providers, server, tunnel, API keys, Git sync

Notebooks

Organise your notes in a familiar binder-style hierarchy. MiloNotes supports unlimited nesting depth — Notebooks → Sections → Sub-Sections → Notes.

The bookshelf

The Notebooks tab shows your notebooks as physical book covers in a 2-column grid. Each cover displays the notebook's color, emoji icon, name, and note count. Tap a cover to open its file tree.

File tree

Inside a notebook you see a collapsible file tree. Tap a folder to expand/collapse it. Tap a note to open it in the editor. Long-press for the context menu.

💡
Tip: Notes can live at any level of the hierarchy — directly in a notebook, or nested inside multiple sub-sections.

Creating structure

  • Tap + (notebook icon) in the Notebooks screen to create a new notebook — choose a name, emoji, and color.
  • Inside a notebook, use the Create New Folder button in the top bar to add a section.
  • Long-press any folder to add a sub-section, rename, or delete it.
  • Notes can be moved between notebooks and folders via long-press → Move.

Web UI (file explorer)

The web interface mirrors the mobile tree view — hover over any row to reveal New Note, New Folder, and action buttons. Click a note to open it in the editor.

Markdown Editor

MiloNotes uses a full Markdown editor with live preview, powered by CodeMirror 6 in the web UI and a native editor on Android.

Supported syntax

SyntaxExample
Headings# H1 ## H2 ### H3
Bold / Italic**bold** *italic* ***both***
Lists- item 1. item - [x] task
Code`inline` ```lang block```
TablesGFM pipe tables
Links[text](url)
Images![alt](url)
Blockquotes> quote
Horizontal rule---

Mermaid diagrams

Wrap Mermaid markup in a fenced code block with the mermaid language identifier:

```mermaid
flowchart LR
  A[Start] --> B{Decision}
  B -- Yes --> C[Do thing]
  B -- No --> D[Skip]
```

Supported diagram types: flowchart, sequenceDiagram, gantt, mindmap, classDiagram, erDiagram.

Mindmaps

Use the mindmap or markmap language identifier — it routes to Mermaid's native mindmap renderer:

```mindmap
root((MiloNotes))
  Features
    Notebooks
    Kanban
    AI
  Access
    LAN
    Tunnel
```

Wikilinks & autocomplete

Type [[ anywhere in the editor to open an autocomplete dropdown that searches your notes by title prefix. Select a note to insert a [[Note Title]] link. These links are rendered as clickable references and power the Knowledge Graph.

YAML front matter

Notes exported via Git sync include YAML front matter:

---
title: Wave-particle duality
tags: [physics, quantum]
created: 2026-03-20T10:00:00Z
updated: 2026-03-20T14:30:00Z
---

Kanban Boards

Create drag-and-drop task boards to manage projects alongside your notes.

Structure

  • Board — top-level container (e.g. "Sprint 1", "Personal Tasks")
  • Column — a swimlane (e.g. "Backlog", "In Progress", "Done")
  • Card — a task with title, description, due date, and priority

Creating a board

Tap the + button on the Kanban screen. Give the board a title. You can optionally link it to a note — the board will then appear as a linked resource inside that note.

Card properties

PropertyDescription
TitleRequired. Short description of the task.
DescriptionOptional longer text or Markdown notes.
Due dateYYYY-MM-DD format. Shown as a badge on the card.
Prioritylow | medium | high
Linked noteAssociates the card with a specific note.

Moving cards

Drag cards between columns on mobile. In the web UI, use the Move option from the card context menu, or via the REST API.

Knowledge Graph

Visualise the connections between your notes as an interactive force-directed graph.

How it works

Every [[Wikilink]] in your notes creates a directed edge in the graph. The Knowledge Graph screen renders all notes as nodes and all Wikilink relationships as edges, letting you explore how your knowledge is connected.

Using the graph

  • Tap a node — opens the note in the editor
  • Pinch to zoom — zoom in/out on the graph
  • Drag — pan around the graph
  • Node size — larger nodes have more incoming links (more referenced)
💡
The more Wikilinks you use in your notes, the more useful the graph becomes. Link related concepts liberally.

Tags

Organise notes with coloured tags. MiloNotes also suggests tags automatically based on note content.

Creating tags

Tags are created globally in Settings → Tags or inline when editing a note. Each tag has a name and a hex colour.

Applying tags

Open any note → tap the tag icon in the toolbar → select tags from the list. Tags appear as coloured pills in note list views.

AI tag suggestions

After saving a note, MiloNotes analyses the content and suggests relevant tags. Suggested tags appear in a "Suggested" section in the tag panel. Tap Accept to apply or Dismiss to ignore.

Day Planner

The Day View is a calendar-integrated daily planner that is the home screen of MiloNotes.

Calendar strip

A horizontal scrollable strip shows the current week. Tap any date to jump to that day's entries.

Day entries

Three types of quick entries:

TypeDescription
📝 NoteQuick freeform text note for the day
⏰ ReminderA timed reminder shown in the calendar strip
☑️ To-doA checkable task item

Note activity feed

Below your entries, MiloNotes shows which notes you opened or edited today — a passive log of your note activity per day.

AI daily summary

At the end of the day (or on demand), Milo AI generates a short summary of your day's entries and note activity. The summary is stored per-day and can be reviewed later.

Attachments

Attach images and files to any note. Attachments are stored on your device and served by the built-in web server.

Attaching files

In the note editor, tap the Attach button in the toolbar. Choose an image from your gallery or files app. The file is saved to the app's private storage and a Markdown image reference is inserted at the cursor.

Supported types

Images: image/png, image/jpeg, image/gif, image/webp, image/svg+xml. Other binary files can be attached but are not rendered inline.

Uploading via API

Use the Attachments API to upload files programmatically. The API returns a URL (/api/attachments/{id}/file) you can embed in Markdown.

💡
Attachments are stored in the app's private directory. They are not included in Git sync, but are included in the ZIP export.

Git Sync

Push all your notes to a Git repository as Markdown files. Works with GitHub, GitLab, Gitea, or any Git host.

Setup

  1. Go to Settings → Git Sync
  2. Enter your repository URL (HTTPS or SSH)
  3. Choose authentication: Personal Access Token or SSH key
  4. Set the branch (default: main)
  5. Tap Sync Now to do a first clone/push

File structure

Notes are written as {notebook}/{section}/{note-title}.md with YAML front matter. A milo-settings.json file at the root stores notebook and tag metadata.

Auto-sync

Enable Auto-sync in Git settings to push changes automatically when your phone has a network connection. Uses WorkManager with a CONNECTED network constraint.

⚠️
SSH private keys and Personal Access Tokens are stored encrypted via the Android Keystore. They are never written to disk in plaintext.

AI Features

MiloNotes has a layered AI system — on-device inference as the privacy-safe default, with optional external providers for more power.

On-device AI (Gemini Nano)

Devices running Android 15 with Gemini Nano available (Pixel 9 series, Samsung Galaxy S25 series) get fully offline AI:

  • Chat with Milo about your notes
  • Note summarisation
  • Content rewriting and improvement
  • Key points extraction
  • AI-suggested tags
⚠️
On-device AI inference requires the app to be in the foreground. If you access the web UI while the app is backgrounded, AI endpoints return 503 Service Unavailable.

External AI providers

Connect any OpenAI-compatible API in Settings → AI Providers:

ProviderTypeBase URL
OllamaLocal LLMhttp://localhost:11434
OpenAICloudhttps://api.openai.com/v1
AnthropicCloudhttps://api.anthropic.com
Google GeminiCloudhttps://generativelanguage.googleapis.com/v1beta
Open WebUISelf-hostedYour Open WebUI URL
Any OpenAI-compatibleAnyCustom base URL

Task routing

In Settings → AI Task Defaults, choose which provider handles each task type — chat, summarise, rewrite, key points, and embeddings — independently.

Memory system

Milo AI extracts key facts from your notes into a memory layer — a structured set of facts about you, your projects, and your knowledge. These memories persist across sessions and provide Milo with long-term context for personalised answers.

Browse and delete memories in Settings → Memories.

Semantic embeddings

Every note is indexed with a MediaPipe MiniLM TFLite embedding vector stored on-device. These power semantic search and RAG (Retrieval-Augmented Generation) — ensuring Milo's answers reference your actual notes.

LAN Server

MiloNotes runs a built-in Ktor web server that serves the full React web app and REST API over your local Wi-Fi network.

Enabling

  1. Go to Settings → Web Server
  2. Toggle Enable LAN Server on
  3. The server starts immediately and shows the local URL (e.g. http://192.168.1.42:8080)

Accessing the web UI

Open the displayed URL in any browser on the same Wi-Fi network. The web app is a full-featured React PWA — you can install it to your desktop or home screen for a native-like experience.

Port

Default port is 8080. This can be changed in Settings. Make sure no firewall on your phone blocks incoming connections on that port.

💡
The server runs as an Android Foreground Service. It stays active while MiloNotes is open or the notification is visible. AI features require the app to be in the foreground.

Internet Tunnel

Access your notes from anywhere in the world via a personal HTTPS URL — no VPN, no port-forwarding, no router config.

How it works

MiloNotes opens a WebSocket connection to tunnel.milonotes.com. The relay server assigns you a permanent subdomain based on your API key and forwards incoming HTTP traffic back to your phone over the WebSocket using yamux multiplexing.

🔒
The relay only forwards raw encrypted bytes — it cannot read your note content. All data is end-to-end encrypted via TLS.

Your personal URL

Your URL is derived deterministically from your API key:

subdomain = "app" + first 8 hex chars of SHA-256(salt + apiKey)
URL       = https://app{subdomain}.milonotes.com

The same device and API key always produce the same URL.

Enabling

  1. Enable the LAN Server first
  2. Go to Settings → Internet Tunnel
  3. Select MiloNotes Tunnel as the provider
  4. Toggle it on — your personal URL is displayed once connected

Cloudflare Tunnel (BYOK)

Use your own Cloudflare account for the tunnel — bring your API token and get a custom hostname on your own domain.

Prerequisites

  • A Cloudflare account with a domain managed there
  • A Cloudflare API token with Zone:DNS:Edit and Account:Cloudflare Tunnel:Edit permissions

Setup

  1. Go to Settings → Internet Tunnel
  2. Select Cloudflare as the tunnel provider
  3. Enter your CF API Token, Account ID, Tunnel Name, and Hostname
  4. Tap Connect — MiloNotes downloads the cloudflared binary for your device ABI and starts the tunnel

How it works

MiloNotes automatically:

  • Creates (or reuses) a named Cloudflare tunnel via the API
  • Configures a DNS CNAME to point your hostname at the tunnel
  • Downloads and runs cloudflared as a background process

API Overview & Authentication

MiloNotes exposes a full REST API at /api/v1/ with JSON request and response bodies.

Enabling the API

  1. Enable the LAN Server in Settings
  2. Go to Settings → Developer Access
  3. Toggle REST API on

Base URLs

ContextBase URL
LANhttp://<device-ip>:8080/api/v1
Internet tunnelhttps://appXXXXXXXX.milonotes.com/api/v1

Authentication

All endpoints require an API key. Create one in Settings → API Keys. Keys are stored as SHA-256 hashes — the plaintext is only shown once at creation.

Pass the key in one of these headers:

X-Api-Key: your-api-key-here
# or
Authorization: Bearer your-api-key-here

Requests without a valid key receive 401 Unauthorized.

Interactive docs

Navigate to http://<device-ip>:8080/api in your browser for an interactive reference with try-it-out buttons for every endpoint.

Response format

All responses are JSON. Errors use this shape:

{ "error": "Unauthorized" }
{ "error": { "message": "Note not found", "type": "not_found" } }

Quick start

KEY="your-api-key"
BASE="http://192.168.1.42:8080/api/v1"

# List all notes
curl -H "X-Api-Key: $KEY" $BASE/notes

# Create a note
curl -X POST -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"title":"Hello","content":"# Hello World"}' $BASE/notes

# Search
curl -H "X-Api-Key: $KEY" "$BASE/search?q=quantum"

Notes API

GET/api/v1/notes

List all non-trashed notes. Filter with query parameters.

ParamTypeDescription
notebookIdstringFilter by notebook
folderIdstringFilter by folder

Returns: array of NoteDto

GET/api/v1/notes/search-title?q={prefix}

Fast title-prefix search (autocomplete). Returns: array of NoteDto.

GET/api/v1/notes/{id}

Get a single note. Returns: NoteDto. Errors: 404.

POST/api/v1/notes

Create a note. Returns 201 Created with NoteDto.

{
  "title": "My Note",          // required
  "content": "# Markdown…",   // optional
  "notebookId": "nb-id",      // optional
  "folderId": "folder-id"     // optional
}
PUT/api/v1/notes/{id}

Update a note. All fields are optional (patch semantics).

{
  "title": "New Title",
  "content": "Updated content",
  "notebookId": "new-nb-id",
  "folderId": "new-folder-id",
  "isPinned": true
}
DELETE/api/v1/notes/{id}

Delete a note. Returns 204 No Content.

NoteDto schema

{
  "id": "string",
  "title": "string",
  "content": "string",          // Markdown
  "notebookId": "string|null",
  "folderId": "string|null",
  "isPinned": false,
  "wordCount": 42,
  "aiSummary": "string|null",
  "tags": [{ "id": "…", "name": "…", "color": "…" }],
  "suggestedTags": [{ "id": "…", "name": "…", "color": "…" }],
  "hasAttachments": false,
  "createdAt": 1710000000000,   // Unix ms
  "updatedAt": 1710000000000
}

Notebooks API

GET/api/v1/notebooks

List all notebooks. Returns: array of NotebookDto.

GET/api/v1/notebooks/{id}

Get a single notebook. Errors: 404.

POST/api/v1/notebooks

Create a notebook. Returns 201 Created.

{ "name": "Physics", "color": "#6750A4", "icon": "⚛️" }
PUT/api/v1/notebooks/{id}

Update a notebook. All fields optional.

{ "name": "New Name", "color": "#FF5722", "icon": "🔥" }
DELETE/api/v1/notebooks/{id}

Delete a notebook. Returns 204 No Content.

NotebookDto schema

{
  "id": "string",
  "name": "string",
  "color": "#6750A4",
  "icon": "📓",
  "sortOrder": 0,
  "createdAt": 1710000000000,
  "updatedAt": 1710000000000
}

Folders API

Folders represent sections and sub-sections within a notebook. They support infinite nesting via parentFolderId.

GET/api/v1/notebooks/{notebookId}/folders

Get root folders (or children of a parent) for a notebook.

ParamDescription
parentIdIf set, returns children of this folder instead of root folders
GET/api/v1/notebooks/{notebookId}/folders/all

All folders in a notebook as a flat list — useful for building a tree client-side.

GET/api/v1/folders/{id}

Get a single folder.

POST/api/v1/notebooks/{notebookId}/folders

Create a folder inside a notebook. Returns 201 Created.

{ "name": "Chapter 1", "parentFolderId": "parent-id-or-null" }
PUT/api/v1/folders/{id}

Rename or move a folder.

{ "name": "New Name", "parentFolderId": "new-parent-or-null" }
DELETE/api/v1/folders/{id}

Delete a folder and its children. Returns 204 No Content.

FolderDto schema

{
  "id": "string",
  "name": "string",
  "notebookId": "string",
  "parentFolderId": "string|null",
  "sortOrder": 0,
  "createdAt": 1710000000000,
  "updatedAt": 1710000000000
}

Tags API

GET/api/v1/tags

List all tags.

POST/api/v1/tags

Create a tag. Returns 201 Created.

{ "name": "important", "color": "#FF5722" }
DELETE/api/v1/tags/{id}

Delete a tag. Returns 204 No Content.

GET/api/v1/notes/{noteId}/tags

Get tags on a note.

PUT/api/v1/notes/{noteId}/tags

Replace all tags on a note.

{ "tagIds": ["tag-id-1", "tag-id-2"] }
POST/api/v1/notes/{noteId}/tags/{tagId}

Add a single tag to a note. Returns 204 No Content.

DELETE/api/v1/notes/{noteId}/tags/{tagId}

Remove a tag from a note. Returns 204 No Content.

POST/api/v1/notes/{noteId}/tags/suggestions/{tagId}/accept

Accept an AI-suggested tag. Returns 204 No Content.

DELETE/api/v1/notes/{noteId}/tags/suggestions/{tagId}

Dismiss an AI-suggested tag. Returns 204 No Content.

Kanban API

Boards

GET/api/v1/kanban/boards
POST/api/v1/kanban/boards
{ "title": "Sprint 1", "linkedNoteId": "note-id-or-null" }
GET/api/v1/kanban/boards/{boardId}
DELETE/api/v1/kanban/boards/{boardId}

Columns

GET/api/v1/kanban/boards/{boardId}/columns
POST/api/v1/kanban/boards/{boardId}/columns
{ "title": "In Progress", "color": "#6366f1", "sortOrder": 1 }
PUT/api/v1/kanban/columns/{columnId}
DELETE/api/v1/kanban/columns/{columnId}

Cards

GET/api/v1/kanban/boards/{boardId}/cards
POST/api/v1/kanban/boards/{boardId}/columns/{columnId}/cards
{
  "title": "Fix login bug",
  "description": "Details…",
  "dueDate": "2026-04-01",
  "priority": "high",          // low | medium | high
  "sortOrder": 0
}
PUT/api/v1/kanban/cards/{cardId}

Update any card field. Move to another column by setting columnId.

POST/api/v1/kanban/cards/{cardId}/move
{ "newColumnId": "column-id", "sortOrder": 2 }
DELETE/api/v1/kanban/cards/{cardId}

Attachments API

POST/api/v1/attachments/upload

Upload a file and attach it to a note. Uses multipart/form-data.

FieldTypeDescription
noteIdstringID of the note to attach to
filefileThe file to upload
curl -X POST -H "X-Api-Key: $KEY" \
  -F "noteId=your-note-id" \
  -F "file=@/path/to/image.png" \
  $BASE/attachments/upload

Returns 201 Created with AttachmentDto including the url field to use in Markdown.

GET/api/v1/attachments/{id}/file

Download raw file bytes. Cached with Cache-Control: immutable.

GET/api/v1/attachments/note/{noteId}

List all attachments for a note.

DELETE/api/v1/attachments/{id}

Delete attachment — removes the DB record and the file on disk. Returns 204.

AttachmentDto schema

{
  "id": "string",
  "noteId": "string",
  "fileName": "image.png",
  "mimeType": "image/png",
  "sizeBytes": 48210,
  "url": "/api/attachments/id/file",
  "createdAt": 1710000000000
}

Chat Completions API

OpenAI-compatible chat completion endpoint. Routes through the AI provider configured in Settings and augments prompts with RAG context from your notes.

POST/api/v1/chat/completions

Request body

{
  "model": "llama3.2",         // model name for the provider
  "messages": [
    { "role": "system",  "content": "You are a helpful assistant." },
    { "role": "user",    "content": "Summarise my quantum physics notes." }
  ],
  "stream": false,             // true for SSE streaming
  "temperature": 0.7,          // optional
  "max_tokens": 1024           // optional
}
💡
Set model to "provider-type:name:model" (e.g. "ollama:local:llama3.2") to target a specific provider. Otherwise the task default is used.

Non-streaming response

{
  "id": "chatcmpl-xxxx",
  "object": "chat.completion",
  "created": 1710000000,
  "model": "llama3.2",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "Here is a summary…" },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
}

Streaming response ("stream": true)

Returns a Server-Sent Events stream. Each event is a JSON chunk:

data: {"id":"chatcmpl-x","object":"chat.completion.chunk","created":…,
       "model":"llama3.2","choices":[{"index":0,"delta":{"content":"Here"},"finish_reason":null}]}

data: {"id":"chatcmpl-x","choices":[{"delta":{"content":" is"},"finish_reason":null}]}

data: [DONE]

Error responses

StatusMeaning
503No AI provider configured, or app is backgrounded (on-device AI requires foreground)
500The AI provider returned an error

Memo Wall API

Quick-capture sticky notes with checklists, colors, pinning, and tags.

GET/api/v1/memo-wall

List all memos, sorted pinned first then by updatedAt descending. Returns: array of MemoDto.

POST/api/v1/memo-wall

Create a memo. Returns 201 Created with MemoDto.

{
  "title": "Shopping List",       // required
  "content": "Milk, eggs, bread", // required
  "color": "#fff9c4",             // optional
  "isPinned": false               // optional
}
GET/api/v1/memo-wall/{id}

Get a single memo with tags. Errors: 404.

PUT/api/v1/memo-wall/{id}

Update a memo. All fields optional (patch semantics).

{
  "title": "Updated Title",
  "content": "New content",
  "color": "#c8e6c9",
  "isPinned": true
}
DELETE/api/v1/memo-wall/{id}

Delete a memo. Returns 204 No Content.

PATCH/api/v1/memo-wall/{id}/toggle/{index}

Toggle a checklist item at the given index. The memo's content must be a JSON array of {text, checked} objects. The item at index has its checked boolean flipped.

PUT/api/v1/memo-wall/{id}/tags

Replace all tags on a memo.

{ "tagIds": ["tag-id-1", "tag-id-2"] }
GET/api/v1/memo-wall/{id}/tags

Get tag IDs for a memo. Returns: array of tag ID strings.

MemoDto schema

{
  "id": "string",
  "title": "Shopping List",
  "content": "[{\"text\":\"Milk\",\"checked\":false}]",
  "color": "#fff9c4",
  "isPinned": true,
  "createdAt": 1711000000000,   // Unix ms
  "updatedAt": 1711000000000,
  "tags": ["tag-id-1"]
}
💡
Memo content can be plain text or a JSON array of checklist items. When it's a checklist, use the PATCH …/toggle/{index} endpoint to check/uncheck items.

Goals API

Track goals with milestones, due dates, priority levels, and linked notes.

Goals

GET/api/v1/goals

List all goals with embedded milestones. Returns: array of GoalDto.

POST/api/v1/goals

Create a goal. Returns 201 Created with GoalDto.

{
  "title": "Launch v1.0",              // required
  "description": "Ship first release", // optional
  "dueDate": "2026-04-15",             // required (YYYY-MM-DD)
  "priority": "high",                  // optional: low | medium | high
  "linkedNoteId": "note-id-or-null",   // optional
  "color": "#6366f1"                   // optional
}
GET/api/v1/goals/{id}

Get a single goal with milestones. Errors: 404.

PUT/api/v1/goals/{id}

Update a goal. All fields optional (patch semantics).

{
  "title": "Launch v2.0",
  "description": "Updated description",
  "dueDate": "2026-05-01",
  "priority": "medium",
  "status": "active",             // active | completed | archived
  "linkedNoteId": "new-note-id",
  "color": "#22c55e"
}
DELETE/api/v1/goals/{id}

Delete a goal. All milestones are cascade-deleted. Returns 204 No Content.

GET/api/v1/goals/due/{date}

Get goals and milestones due on a specific date (YYYY-MM-DD).

Milestones

POST/api/v1/goals/{id}/milestones

Add a milestone to a goal. Returns 201 Created.

{
  "title": "Beta testing",   // required
  "dueDate": "2026-04-01",   // optional (YYYY-MM-DD)
  "sortOrder": 0              // optional
}
PUT/api/v1/goals/{goalId}/milestones/{milestoneId}

Update a milestone. All fields optional.

DELETE/api/v1/goals/{goalId}/milestones/{milestoneId}

Delete a milestone. Returns 204 No Content.

PATCH/api/v1/goals/{goalId}/milestones/{milestoneId}/toggle

Toggle a milestone's completion status. Flips isCompleted between true and false.

GoalDto schema

{
  "id": "string",
  "title": "Launch v1.0",
  "description": "Ship the first public release",
  "dueDate": "2026-04-15",
  "priority": "high",              // low | medium | high
  "status": "active",              // active | completed | archived
  "linkedNoteId": "string|null",
  "color": "#6366f1",
  "milestones": [
    {
      "id": "string",
      "goalId": "string",
      "title": "Beta testing",
      "dueDate": "2026-04-01",
      "isCompleted": true,
      "sortOrder": 0,
      "createdAt": 1711000000000
    }
  ],
  "createdAt": 1711000000000,      // Unix ms
  "updatedAt": 1711000000000
}

AI Learn API

Upload a file for Milo to read, analyze, and generate notes from. Supports PDFs, CSVs, images (OCR), and text files.

POST/api/v1/ai/learn

Upload a file via multipart/form-data. Returns a Server-Sent Events (SSE) stream as Milo processes the file and generates content.

FieldTypeDescription
filefileThe file to process (PDF, CSV, image, or text file)

Supported file types

TypeExtensionsProcessing
PDF.pdfText extraction and analysis
CSV.csvTabular data analysis
Images.png, .jpg, .webpOCR text extraction
Text.txt, .mdDirect content analysis

Example

curl -X POST -H "X-Api-Key: $KEY" \
  -F "file=@/path/to/document.pdf" \
  $BASE/ai/learn

SSE response

The response is a streaming event source. Each frame contains a JSON delta:

data: {"delta":"# Summary of document\n\n"}
data: {"delta":"The document covers…"}
data: {"delta":"…key findings include…"}
data: [DONE]
⚠️
AI Learn requires an AI provider to be configured and the app to be in the foreground. Returns 503 if no provider is available.

Chat History API

Manage AI chat conversation history. Conversations are stored locally and can be listed, retrieved, or deleted.

GET/api/v1/chat/history

List all chat conversations. Returns an array of conversation summaries ordered by most recent.

GET/api/v1/chat/history/{conversationId}

Get all messages in a specific conversation.

DELETE/api/v1/chat/history/{conversationId}

Delete a conversation and all its messages. Returns 204 No Content.

DELETE/api/v1/chat/history

Delete all chat history. Returns 204 No Content.

Memories API

The memory system extracts key facts from conversations and stores them as persistent memories. These memories are used to provide context-aware AI responses over time.

GET/api/v1/ai/memories

List all stored memories.

DELETE/api/v1/ai/memories/{id}

Delete a specific memory. Returns 204 No Content.

POST/api/v1/ai/memories/consolidate

Trigger memory consolidation — the AI reviews all memories and merges duplicates, removes outdated entries, and flattens the memory store. Returns a summary of changes.

POST/api/v1/ai/memories/import

Import memories from other AI providers (ChatGPT, Claude, etc.). Accepts a text blob and auto-flattens into individual memory entries.

{ "content": "User prefers dark mode.\nUser is a physics student.\n..." }

MemoryDto schema

{
  "id": "string",
  "content": "User prefers dark mode for all applications",
  "source": "conversation",   // conversation | import
  "createdAt": 1711000000000,
  "updatedAt": 1711000000000
}

Feature Toggles API

Control which modules are enabled in the app. Users can enable or disable features like Kanban, Goals, Memo Wall, Pomodoro Timer, and Knowledge Graph.

GET/api/v1/settings/features

Get the current state of all feature toggles.

{
  "kanban": true,
  "goals": true,
  "memoWall": true,
  "pomodoro": true,
  "knowledgeGraph": true,
  "dayPlanner": true,
  "gitSync": false,
  "aiChat": true
}
PUT/api/v1/settings/features

Update feature toggles. Only include the features you want to change.

{ "pomodoro": false, "gitSync": true }

MCP Server Overview

MiloNotes implements the Model Context Protocol (MCP) — allowing AI assistants like Claude, Cursor, and other MCP-compatible clients to read and manage your notes directly.

What is MCP?

The Model Context Protocol is an open standard that lets AI tools connect to external data sources and services. When an AI client connects to the MiloNotes MCP server, it gets tools it can call to list notes, create notes, search, and more — turning Milo into an active context source for any AI assistant.

Protocol details

DetailValue
TransportHTTP (JSON-RPC 2.0)
Protocol version2024-11-05
Endpoint/mcp
AuthenticationX-Api-Key header
DiscoveryGET /mcp returns server info
RPCPOST /mcp handles all method calls

MCP URLs

ContextURL
LANhttp://<device-ip>:8080/mcp
Internet tunnelhttps://appXXXXXXXX.milonotes.com/mcp

MCP Server Setup

Enabling the MCP server

  1. Enable the LAN Server in Settings
  2. Go to Settings → Developer Access
  3. Toggle MCP Server on
  4. Create an API Key in Settings → API Keys if you haven't already

Verify the server is running

curl http://192.168.1.42:8080/mcp
# Returns:
{
  "name": "MiloNotes",
  "version": "1.0.0",
  "protocolVersion": "2024-11-05",
  "transport": "http",
  "endpoint": "/mcp"
}

Making a JSON-RPC call

curl -X POST http://192.168.1.42:8080/mcp \
  -H "X-Api-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

Connecting Claude Desktop

Configuration file

Find your Claude Desktop config file:

OSPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json

Add MiloNotes

Add the following to the mcpServers object:

{
  "mcpServers": {
    "milonotes": {
      "url": "http://192.168.1.42:8080/mcp",
      "headers": {
        "X-Api-Key": "your-api-key-here"
      }
    }
  }
}
💡
For remote access, replace the LAN URL with your tunnel URL: https://appXXXXXXXX.milonotes.com/mcp

Restart Claude Desktop

After saving the config, restart Claude Desktop. You should see MiloNotes listed in the MCP tools panel (hammer icon). Claude can now use your notes as context automatically.

Example prompts

  • "Search my notes for anything about quantum mechanics"
  • "Create a note called 'Meeting 2026-04-01' with these bullet points…"
  • "List my notebooks and find the one about computer science"
  • "Create a Kanban card in my Dev board to refactor the auth module"

Connecting Cursor

Via Cursor settings UI

  1. Open Cursor → SettingsMCP
  2. Click Add Server
  3. Fill in the fields:
    • Name: MiloNotes
    • URL: http://192.168.1.42:8080/mcp
    • Headers: X-Api-Key: your-api-key-here
  4. Click Save

Via .cursor/mcp.json

Alternatively, add to your project's .cursor/mcp.json:

{
  "mcpServers": {
    "milonotes": {
      "url": "http://192.168.1.42:8080/mcp",
      "headers": {
        "X-Api-Key": "your-api-key-here"
      }
    }
  }
}

Cursor will now include MiloNotes tools in its AI context. You can ask Cursor to look up your notes when working on code — e.g. "Check my notes for the API design I documented last week."

Available MCP Tools

MiloNotes exposes the following tools via tools/list and tools/call.

list_notes

List notes, optionally filtered by notebook or capped at a limit.

ArgumentTypeRequiredDescription
notebookIdstringNoFilter to a notebook
limitintegerNoMax results (default: 20)
get_note

Fetch a note's full Markdown content by ID.

ArgumentTypeRequiredDescription
idstringYesNote ID
create_note

Create a new note.

ArgumentTypeRequiredDescription
titlestringYesNote title
contentstringNoMarkdown body
notebookIdstringNoPlace in a notebook
folderIdstringNoPlace in a folder
update_note

Update an existing note's title or content.

ArgumentTypeRequiredDescription
idstringYesNote ID
titlestringNoNew title
contentstringNoNew Markdown content
search_notes

Full-text search across all notes.

ArgumentTypeRequiredDescription
querystringYesSearch query
limitintegerNoMax results (default: 10)
list_notebooks

List all notebooks with their emoji icons. No required arguments.

list_kanban_boards

List all Kanban boards. No required arguments.

create_kanban_card

Create a card on a Kanban board.

ArgumentTypeRequiredDescription
boardIdstringYesBoard ID
columnIdstringYesColumn to place the card in
titlestringYesCard title
descriptionstringNoCard body text
prioritystringNolow | medium | high
chat_completion

Send a message through the configured AI provider on the device. Automatically augmented with RAG context from your notes.

ArgumentTypeRequiredDescription
messagestringYesThe user message to send
modelstringNoProvider key (e.g. ollama:local:llama3.2)
historystringNoJSON array of prior {role, content} messages
move_kanban_card

Move a Kanban card to a different column.

ArgumentTypeRequiredDescription
cardIdstringYesCard ID
newColumnIdstringYesTarget column ID
sortOrderintegerNoPosition in the new column
update_kanban_card

Update a Kanban card's title, description, priority, or due date.

ArgumentTypeRequiredDescription
cardIdstringYesCard ID
titlestringNoNew title
descriptionstringNoNew description
prioritystringNolow | medium | high
dueDatestringNoDue date (YYYY-MM-DD)

Memo Wall tools

list_memos

List all memos sorted by pinned status and recency. No required arguments.

create_memo

Create a new memo on the Memo Wall.

ArgumentTypeRequiredDescription
titlestringYesMemo title
contentstringYesText or JSON checklist array
colorstringNoHex color (e.g. #fff9c4)
isPinnedbooleanNoPin to top of wall
update_memo

Update an existing memo.

ArgumentTypeRequiredDescription
idstringYesMemo ID
titlestringNoNew title
contentstringNoNew content
colorstringNoNew color
isPinnedbooleanNoPin state
delete_memo

Delete a memo from the Memo Wall.

ArgumentTypeRequiredDescription
idstringYesMemo ID
toggle_memo_checklist

Toggle a checklist item in a memo by index.

ArgumentTypeRequiredDescription
idstringYesMemo ID
indexintegerYesChecklist item index (0-based)

Goals tools

list_goals

List all goals with their milestones. No required arguments.

create_goal

Create a new goal.

ArgumentTypeRequiredDescription
titlestringYesGoal title
dueDatestringYesDue date (YYYY-MM-DD)
descriptionstringNoGoal description
prioritystringNolow | medium | high
linkedNoteIdstringNoLink to a note
colorstringNoHex color
update_goal

Update an existing goal.

ArgumentTypeRequiredDescription
idstringYesGoal ID
titlestringNoNew title
descriptionstringNoNew description
dueDatestringNoNew due date
prioritystringNoNew priority
statusstringNoactive | completed | archived
colorstringNoNew color
delete_goal

Delete a goal and all its milestones.

ArgumentTypeRequiredDescription
idstringYesGoal ID
add_milestone

Add a milestone to a goal.

ArgumentTypeRequiredDescription
goalIdstringYesParent goal ID
titlestringYesMilestone title
dueDatestringNoDue date (YYYY-MM-DD)
sortOrderintegerNoDisplay order
toggle_milestone

Toggle a milestone's completion status.

ArgumentTypeRequiredDescription
goalIdstringYesParent goal ID
milestoneIdstringYesMilestone ID

Memory tools

search_memories

Search the AI memory store for relevant memories. Useful for checking what the AI remembers about specific topics.

ArgumentTypeRequiredDescription
querystringYesSearch query
limitintegerNoMax results (default: 10)
list_memories

List all stored AI memories. No required arguments.

Error codes

CodeMeaning
-32700Parse error — malformed JSON
-32601Method not found
-32603Internal error (note not found, missing required argument, AI unavailable)