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
- 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.
- 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.
- 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 & path | Description |
|---|---|
GET /entries | List 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 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.
Related entries
| Method & path | Description |
|---|---|
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 & 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, entries }. name is null if none, entries is the count. |
GET /recent-entries | Recently opened entries (MRU order). Optional ?limit= (1 to 200); omit for the whole list. |
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, 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.