# Agent Notes > A tiny HTTP sticky-note store for AI agents. Save Markdown notes with file attachments, edit them, find them again, > and hand a human a rendered preview link. Plain JSON over HTTPS, full-text search (Chinese/English/any language, > substring match), tags, optimistic locking. Base URL: https://notes.peidayu.com Auth: every /notes and /tags call needs the key. Send header `Authorization: Bearer ` (also accepted: `X-API-Key: ` or `?key=`). The key is NOT in this file; your operator gives it to you. Only this file (/llms.txt) and /health are public. Everything else — API, preview pages, attachments — requires the key. ## Data model A note: ```json { "id": "k3m9x2pq", // auto-generated, or your own: [A-Za-z0-9][A-Za-z0-9_.-]{0,63} "title": "string (≤500 chars, may be empty)", "content": "Markdown (GFM) text, ≤1,000,000 chars", "tags": ["lowercase", "deduped"], // ≤32 tags, each ≤64 chars "version": 3, // +1 on every write; use it to avoid clobbering others "created_at": "ISO-8601 UTC", "updated_at": "ISO-8601 UTC", "view_url": "https://notes.peidayu.com/notes/k3m9x2pq/view", // rendered HTML preview for humans (needs the key) "files": [ { "name", "size", "type", "url", "markdown" } ] // on single-note responses; // list items carry "file_count" instead } ``` ## Endpoints | Method | Path | Purpose | |---|---|---| | POST | /notes | create (auto id, or pass "id") → 201; 409 if id exists | | GET | /notes | list / search / filter by tag | | GET | /notes/{id} | read one (JSON; add ?format=text for raw content) | | PUT | /notes/{id} | upsert: create or fully replace title/content/tags | | PATCH | /notes/{id} | partial edit: fields, append/prepend, find-replace, tag add/remove | | DELETE | /notes/{id} | delete (its attachments too) | | GET | /notes/{id}/view | rendered Markdown preview page (HTML, for humans) | | PUT | /notes/{id}/files/{name} | upload/overwrite one attachment, raw body | | POST | /notes/{id}/files | upload one or more attachments, multipart/form-data | | GET | /notes/{id}/files | list attachments | | GET | /notes/{id}/files/{name} | download (supports Range; ?download=1 forces save) | | DELETE | /notes/{id}/files/{name} | delete attachment | | GET | /tags | all tags with counts | ### Create ```bash curl -s https://notes.peidayu.com/notes -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \ -d '{"title":"deploy checklist","content":"1. run tests\n2. wrangler deploy","tags":["ops","todo"]}' ``` Pick a stable id when you want a fixed "slot" you can overwrite later (e.g. your working memory): ```bash curl -s -X PUT https://notes.peidayu.com/notes/my-agent.memory -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \ -d '{"title":"working memory","content":"..."}' ``` Shortcut: a non-JSON body is taken as content; title/tags/id come from the query string: `curl -s https://notes.peidayu.com/notes?title=log&tags=a,b -H "Authorization: Bearer $KEY" --data-binary @file.md` ### Search & list — GET /notes Query params (all optional): - `q` full-text search over title+content+tags. Space-separated terms are ANDed. Case-insensitive substring match, works for Chinese. - `tag` filter; repeat or comma-separate (`tag=ops&tag=todo` or `tag=ops,todo`) → must have ALL - `limit` 1-100, default 20; `offset` default 0 - `sort` `updated` (default) or `created`, newest first. When `q` is given, results are ranked by relevance instead (title matches weigh most). - `full=1` include full `content`; otherwise each item has a 200-char `preview` Response: `{"total": N, "limit", "offset", "notes": [ ... ]}`. With `q`, items also carry `snippet` with matches wrapped in [[ ]]. ```bash curl -s -G https://notes.peidayu.com/notes --data-urlencode "q=部署 wrangler" -d tag=ops -H "Authorization: Bearer $KEY" ``` **Always URL-encode query strings** (use `curl -G --data-urlencode` or your HTTP client's params). Raw non-ASCII characters (e.g. Chinese) in the URL are rejected with HTTP 400 before reaching the service. Note: terms of 1–2 characters use a slower LIKE scan and no ranking; prefer ≥3-char terms. ### Read `GET /notes/{id}` → note JSON, header `ETag: `. `GET /notes/{id}?format=text` → raw content only. ### Edit — PATCH /notes/{id} (JSON body, every field optional, applied in this order) - `title`, `content`, `tags` replace that field - `replace`: `{"old":"exact text","new":"replacement"}` or an array of them. `old` must occur exactly once (422 `replace_not_found` / `replace_ambiguous` otherwise); add `"all":true` to replace every occurrence. - `prepend`, `append` add text to start / end of content (include your own "\n") - `add_tags`, `remove_tags` array or comma string - `version` optional lock: only apply if current version equals this (or send header `If-Match: `) ```bash # append a log line curl -s -X PATCH https://notes.peidayu.com/notes/k3m9x2pq -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \ -d '{"append":"\n- 2026-09-29 deployed v2"}' # safe targeted edit curl -s -X PATCH https://notes.peidayu.com/notes/k3m9x2pq -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \ -d '{"version":4,"replace":{"old":"2. wrangler deploy","new":"2. wrangler deploy --env prod"},"add_tags":["done"]}' ``` Plain-text append shortcut: `PATCH /notes/{id}?mode=append` with a text/plain body. ## Markdown, attachments and images Content is treated as GitHub-flavored Markdown (tables, task lists, fenced code). Attachments belong to a note (≤50 MB each, ≤200 per note; the note must exist first). Name them without spaces; spaces become "-", "/" is removed. Re-uploading the same name overwrites. ```bash # 1) upload (raw body; content-type is guessed from the extension if you don't send one) curl -s -X PUT https://notes.peidayu.com/notes/k3m9x2pq/files/chart.png -H "Authorization: Bearer $KEY" --data-binary @chart.png # → {"name":"chart.png","type":"image/png","url":"…","markdown":"![chart.png](files/chart.png)", ...} # or several at once curl -s https://notes.peidayu.com/notes/k3m9x2pq/files -H "Authorization: Bearer $KEY" -F file=@a.png -F file=@report.pdf # 2) reference it in the Markdown with the RELATIVE path files/ (just paste the "markdown" field) curl -s -X PATCH https://notes.peidayu.com/notes/k3m9x2pq -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \ -d '{"append":"\n\n![chart](files/chart.png)\n\nFull report: [report.pdf](files/report.pdf)"}' ``` Use relative `files/` (not the absolute url) in content: the preview resolves it to this note's attachment. External `https://` images also work. Downloading an attachment yourself needs the key header like any other call. Non-ASCII file names are fine but must be percent-encoded in URLs (`files/%E5%9B%BE.png`); the `markdown` field already is. ## Preview (for humans) `view_url` (`https://notes.peidayu.com/notes/{id}/view`) renders title, tags, the Markdown body with inline images, and an attachment list. It is NOT public: a browser without a session is sent to https://notes.peidayu.com/app, which asks for the key once and then sets a session cookie. There is no key-less share link; only people who have the key can view notes. Humans with the key can also browse, search and view galleries of all notes at https://notes.peidayu.com/app. The preview has no JavaScript (raw HTML in Markdown is shown but scripts don't run). ### Delete `curl -s -X DELETE https://notes.peidayu.com/notes/{id} -H "Authorization: Bearer $KEY"` → `{"deleted":"{id}","files_deleted":N}` (optional `?version=N` lock) ## Errors Always JSON: `{"error":{"code":"...","message":"..."}}` - 400 bad input (bad_id, bad_json, bad_tags...) - 401 unauthorized - 404 not_found - 409 exists (POST with taken id) / version_conflict (someone else wrote first: GET again, re-apply, retry) - 413 too long / file_too_large - 415 need_multipart - 422 replace_not_found / replace_ambiguous ## Tips for agents - Before creating, search (`GET /notes?q=...`) to avoid duplicates; update the existing note instead. - Use tags for grouping (project name, "todo", "memory"), and stable ids for notes you revisit. - For concurrent edits, prefer PATCH with `replace`/`append` over rewriting the whole content, and pass `version`. - Write content as Markdown; put images/files in attachments and link them as `files/`. - When a human (who has the key) should read the note, reply with its `view_url`. Never paste the key into notes or links. - Content is text; to store structured data, put JSON in a fenced code block or upload a .json attachment.