Developer API

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; the API just reads and writes the same Markdown files.

Building for an AI agent? You usually don’t need to touch this API directly — the Notedog MCP server wraps it as ready-made tools. This page is the raw contract underneath.

Authentication

Every /api/v2/* request carries a token in the Authorization header. There are two kinds of token, and both are sent the same way:

Authorization: Bearer <token>

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.

API keys (for scripts & agents)

For anything that isn’t a browser, mint a dedicated key in the app: open the drawer → API keysCreate key. Keys are long-lived (no expiry) and you can revoke any of them at any time from the same screen.

An 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. Scope is enforced on the phone — a read-only key simply can’t mutate anything.

Base URL

Entry shape

Most endpoints return entries in this form:

{
  "path": "entries/2026-05-24.md",  // path inside the journal
  "content": "# ...",                // markdown (preview in lists, full when fetched by path)
  "timestamp": 1748044800000,        // last modified, ms since epoch
  "createdAt": 1748000000000         // created, ms since epoch
}

Endpoints

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

Entries

Method & pathDescription
GET /entriesList entries (preview content), newest first. Query: q (search), 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.

Similarity is computed on the phone itself, not by an external service. Results are 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 } (null if none).
GET /recent-entriesRecently opened entries (MRU order).
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

Examples

Read — search the journal:

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

Write — create an entry (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 as tools so you don’t have to wire up HTTP yourself.