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 keys → Create key. Keys are long-lived (no expiry) and you can revoke any of them at any time 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.
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
- Tunnel —
https://<your-subdomain>.t.notedog.run. Real TLS, works from anywhere. (See remote access.) - LAN —
https://<phone-ip>:<port>, the address shown under On this network. It 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", // 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 & path | Description |
|---|---|
GET /entries | List 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 write | Create. Body { path?, content } (blank path uses your filename template). 201 { path }; 409 on collision; 422 on validation failure. |
PUT /entries/{path} write | Update content, optionally rename. Body { content, newPath? }. 200 { path }; 409 on rename collision. |
DELETE /entries/{path} write | Delete. 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.
Directories
| Method & path | Description |
|---|---|
GET /directories | Journal root: { dirs, files }. |
GET /directories/{path} | A subfolder, same shape. Rejects .. with 400. |
POST /directories write | Create a folder. Body { parent, name } (single segment). 201 { path }; 400/404/409. |
Attachments
| Method & path | Description |
|---|---|
GET /attachments/{path} | Serve an image. Sends ETag/Cache-Control; 304 on a matching If-None-Match. |
POST /attachments write | Upload an image (multipart, field file; png/jpg/gif/webp, max 10 MB). 200 { path }. |
PUT /attachments/{path} write | Overwrite an existing attachment. 200 { path }; 404 if absent. |
DELETE /attachments/{path} write | Delete. 204. |
Recents, preferences & journal
| Method & path | Description |
|---|---|
GET /journal | Active journal: { name } (null if none). |
GET /recent-entries | Recently opened entries (MRU order). |
POST /recent-entries write | Mark an entry opened. Body { path }. |
GET /recent-searches | Up to 5 recent keywords. |
POST /recent-searches write | Track a keyword. Body { keyword }. |
GET /preferences | Editor preferences: { [key]: string }. |
PUT /preferences write | Replace the whole preferences object. |
POST /export | Stream the journal as a ZIP (application/zip). |
Status codes
200/201/204— success.400— bad request (e.g. 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
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.