Connect scripts and AI tools
Let scripts and AI agents read, create and append to notes with an API token kept in an environment variable.
Applies to Requires an API token · macOS or Linux shell, Python 3
These recipes use only the four requests in the Markdown API: list, read as Markdown, create and append. Anything else — overwriting, deleting, searching note bodies — isn’t available through the API.
Keep the token safe
- In the profile menu → Account security → API tokens, create one token per purpose (for example, “lab log script” or “MacBook Claude Code”). If one leaks, you revoke just that one.
- Put it in your shell profile (
~/.zshrcor similar) as an environment variable:export COMMONNOTE_TOKEN="cnk_…" - Have scripts and tools read that variable. Never write the token into a code file.
Shell: append a line to a note
Useful for collecting measurements or a work log in one note. The JSON is built with python3 so line breaks and quotes stay safe.
NOTE_ID="<note-id>"
LINE="- $(date '+%Y-%m-%d %H:%M') Centrifugation done, supernatant stored"
python3 -c 'import json,sys; print(json.dumps({"markdown": sys.argv[1]}))' "$LINE" |
curl -sS -X POST -H "Authorization: Bearer $COMMONNOTE_TOKEN" \
-H "Content-Type: application/json" --data-binary @- \
"https://commonnote.app/api/v1/notes/$NOTE_ID/append"
Python: upload a file as a note, save notes to files
Standard library only.
import json, os, pathlib, urllib.parse, urllib.request
BASE = "https://commonnote.app/api/v1"
TOKEN = os.environ["COMMONNOTE_TOKEN"]
def call(method, path, body=None, accept="application/json"):
req = urllib.request.Request(
BASE + path, method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json", "Accept": accept})
with urllib.request.urlopen(req) as res:
text = res.read().decode()
return json.loads(text) if accept == "application/json" else text
# 1) Upload a Markdown file as a new note
md = pathlib.Path("report.md").read_text(encoding="utf-8")
created = call("POST", "/notes", {"markdown": md})
print(created["url"])
# 2) Save recent notes with "weekly" in the title to files
for note in call("GET", "/notes?limit=20&q=" + urllib.parse.quote("weekly"))["notes"]:
if note["locked"]:
continue
body = call("GET", f"/notes/{note['id']}", accept="text/markdown")
pathlib.Path(f"{note['id']}.md").write_text(body, encoding="utf-8")
- On a 429, wait the number of seconds in the
Retry-Afterheader and retry. The limit is 300 requests per minute per token. - Password-locked notes always return 423 to an API token, even while they’re unlocked in the app, so skip them as above.
- Each request’s markdown must be 2 MB or less.
Hand it to an AI coding agent (such as Claude Code)
Agents that run commands in your terminal, like Claude Code, can use this API directly. Pass the token only as an environment variable in the shell where the agent runs, and tell the agent the variable’s name — never the value.
- In the terminal where you’ll run the agent, check that
COMMONNOTE_TOKENis set without printing it — for example,test -n "$COMMONNOTE_TOKEN" && echo set. - Ask the agent something like:
Use the CommonNote Markdown API (https://commonnote.app/api/v1). The token is in the COMMONNOTE_TOKEN environment variable; send it as an Authorization: Bearer header and never print it or write it to a file. Read my recent notes with "meeting" in the title as Markdown and pull out the action items. - To keep the result, add “create a new note with it” (
POST /notes) or “append it to note <id>” (/append).