The Notedog API

The same on-device HTTPS server that powers browser editing exposes a small REST API under /api/v2. Point your own scripts or an AI agent at it over your local network or the tunnel. Your journal stays on your phone, and the API reads and writes the same Markdown files.

Good to knowBuilding for an AI agent? You usually don't need this API directly. The Notedog MCP server wraps it as ready-made tools. This page documents the HTTP API those tools call.

Make your first request

  1. In Notedog, turn on On this network to connect from the same network, or On the internet (tunnel) for a paid public address. Use the address shown in the app as your base URL.
  2. Open the drawer → API keys → Create key. The default read-only key is enough to list entries. Copy it when it appears; it is shown once.
  3. In a Bash shell, use the local address shown in the app and paste the key at the hidden prompt:
NOTEDOG_BASE_URL="https://<phone-ip>:<port>"
read -rsp 'Notedog API key: ' NOTEDOG_KEY; echo
curl --insecure -H "Authorization: Bearer $NOTEDOG_KEY" \
  "$NOTEDOG_BASE_URL/api/v2/entries?limit=5"

You should get JSON with an entries array and a nextCursor. --insecure accepts the phone's self-signed certificate on your local network. If you use the tunnel instead, replace the base URL with your https://<your-subdomain>.t.notedog.run address and remove --insecure. The key stays in this shell session; don't add it to a project file.

Authentication

Every /api/v2/* request carries a token in the Authorization header. Scripts use API keys; the paired web editor uses short-lived tokens. Both are sent the same way:

Authorization: Bearer <token>

API keys (for scripts & agents)

Create a dedicated key in the app under API keys. Keys are long-lived (no expiry), and you can revoke any of them from the same screen.

Read-only (default)

can read the whole journal, but every write is rejected with 403.

Read-write

turn on Allow writes when creating the key to also allow create, edit, and delete. On a Git-backed journal, each write becomes its own commit (with the key's name in the message), so agent changes are easy to review and revert.

Keep it secretAn API key is a bearer token: anyone holding it has that level of access over your LAN or tunnel. Keep it secret (don't commit it to a repo), prefer read-only unless you need writes, and revoke it in the app if it leaks. The phone enforces scope, so a read-only key can't change anything.

Paired browser tokens

When you open the web editor and approve the pairing prompt, the browser receives a short-lived access token (12 hours) plus a refresh token (30 days, rotated on use). This is what the web UI uses; it's also stored as an HttpOnly cookie.

Base URL

Tunnel

https://<your-subdomain>.t.notedog.run. Real TLS, works from anywhere. Turn on On the internet (tunnel) first; it is paid. (See remote access.)

LAN

https://<phone-ip>:<port>, the address shown when you turn on On this network. Your computer must be on the same network. The phone uses a self-signed certificate, so a command-line client needs to accept it (e.g. curl --insecure).

Entry shape

Most endpoints return entries in this form:

{
  "path": "entries/2026-05-24.md",
  "content": "# ...",
  "timestamp": 1779580800000,
  "createdAt": 1779536000000
}

path is relative to the journal. content is a preview in lists and the full Markdown when fetched by path. Both dates are milliseconds since the Unix epoch.

Endpoints

All paths are under /api/v2. Endpoints marked write require a read-write key (a read-only key gets 403).

Choosing a journal

GET /journals lists every journal as { name, active }. Existing paths such as /entries use the journal active on the phone. Automation can instead prefix the MCP-compatible operations with /journals/{journal}, for example /journals/Work/entries. The explicit journal stays fixed for that request and does not change the phone UI.

The scoped forms cover journal info, entry list/search/get/create/update/delete, related entries, directory listing, recent entries, and Git changes. Journal names are device-wide under the same read/read-write key; there is no per-journal permission setting.

Entries

Method & pathDescription
GET /entriesList entries (preview content), newest first. Query: q (search; may include from:, to:, path:, has:todo, has:attachment and - exclusions), from/to (YYYY-MM-DD), limit (default 50, max 200), cursor. Returns { entries, nextCursor }; nextCursor: null is the last page.
GET /entries/{path}One entry with full content. 404 if missing.
POST /entries writeCreate. Body { path?, content } (blank path uses your filename template). 201 { path }; 409 on collision; 422 on validation failure.
PUT /entries/{path} writeUpdate content, optionally rename. Body { content, newPath? }. 200 { path }; 409 on rename collision.
DELETE /entries/{path} writeDelete. 204 (also if already gone).

When q or from/to are present, results are ranked by relevance (BM25 + a filename-match bonus, ties broken by recency); otherwise newest-first by createdAt.

Related entries

Method & pathDescription
GET /related?path={path}Entries with content similar to the entry at path, even when nothing links them. 400 if path is missing, 404 if it doesn't exist.

The phone computes similarity itself, with no external service. Results come sorted by score, best match first. An empty list means no good match was found:

{
  "related": [
    { "path": "entries/kenya-aa.md", "score": 0.82, "preview": "Dialing in a light roast — grind finer, ..." }
  ]
}

The first call after an edit rebuilds the index, which can take a few seconds on a large journal. The web editor requests related entries in the background, after the entry itself has loaded.

Directories

Method & pathDescription
GET /directoriesJournal root: { dirs, files }.
GET /directories/{path}A subfolder, same shape. Rejects .. with 400.
POST /directories writeCreate a folder. Body { parent, name } (single segment). 201 { path }; 400/404/409.

Attachments

Method & pathDescription
GET /attachments/{path}Serve an image. Sends ETag/Cache-Control; 304 on a matching If-None-Match.
POST /attachments writeUpload an image (multipart, field file; png/jpg/gif/webp, max 10 MB). 200 { path }.
PUT /attachments/{path} writeOverwrite an existing attachment. 200 { path }; 404 if absent.
DELETE /attachments/{path} writeDelete. 204.

Recents, preferences & journal

Method & pathDescription
GET /journalActive journal: { name, entries }. name is null if none, entries is the count.
GET /recent-entriesRecently opened entries (MRU order). Optional ?limit= (1 to 200); omit for the whole list.
POST /recent-entries writeMark an entry opened. Body { path }.
GET /recent-searchesUp to 5 recent keywords.
POST /recent-searches writeTrack a keyword. Body { keyword }.
GET /preferencesEditor preferences: { [key]: string }.
PUT /preferences writeReplace the whole preferences object.
POST /exportStream the journal as a ZIP (application/zip).

Status codes

  • 200 / 201 / 204: success.
  • 400: bad request, for example an invalid cursor or a .. path segment.
  • 401: missing, invalid, or revoked token ("Not paired").
  • 403: a read-only key attempted a write.
  • 404: entry or attachment not found.
  • 409: path collision on create/rename.
  • 422: validation failure.

Examples

Search the journal:

curl -H "Authorization: Bearer $NOTEDOG_KEY" \
  "https://your-name.t.notedog.run/api/v2/entries?q=domain%20driven%20design"

Create an entry. This needs a read-write key:

curl -X POST -H "Authorization: Bearer $NOTEDOG_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"# Meeting notes\n..."}' \
  "https://your-name.t.notedog.run/api/v2/entries"

For AI agents, the Notedog MCP server exposes these endpoints as tools, so you don't have to write the HTTP calls yourself.