Write Note via OpenNote Public API
Write a note to OpenNote using the public API at https://api.opennote.cc/api/v1.
Getting Started
What you need
- 1. An OpenNote account — download the app from the App Store
- A Pro subscription — API access is a Pro feature
- A Personal Access Token (PAT) — generated from the app
Generating an API token
- 1. Open the OpenNote app
- Go to Profile → API Tokens
- Tap Create Token
- Choose the scopes you need:
-
diaries:write — create and update notes
-
images:write — upload images to notes
- 5. Set an expiration (default: 90 days, max: 365 days, or never)
- Copy the token immediately — it starts with
milo_pat_v1_ and is only shown once
Setting up the environment variable
Set OPENNOTE_API_TOKEN in your OpenClaw environment:
Option A — OpenClaw config (recommended):
In your OpenClaw settings, add the environment variable:
CODEBLOCK0
Option B — Shell profile (if running locally):
CODEBLOCK1
Add this to your ~/.zshrc or ~/.bashrc to persist it across sessions.
Security:
- - Never commit your token to version control
- The token grants write access to your notes — treat it like a password
- If compromised, revoke it immediately from Profile → API Tokens in the app
Token lifecycle
- - Tokens expire based on the duration you set (default 90 days)
- You can have up to 5 active tokens per account
- Revoke unused tokens from the app at any time
- When a token expires or is revoked, generate a new one and update INLINECODE7
- If you get a
401 INVALID_API_TOKEN error, your token has expired or been revoked
Prerequisites
The token is read from the OPENNOTE_API_TOKEN environment variable. It must have the diaries:write scope. If images will be uploaded, images:write is also required.
Local Cache Files
This skill maintains two local files in the working directory under .opennote/:
- - Labels cache:
.opennote/opennote-labels-cache.json — cached user labels - Note history:
.opennote/opennote-history.json — log of all notes written/edited via this skill
Always read these files at the start of every invocation. If they are missing, treat them as empty. If the user asks about previously written notes, consult the history file.
Instructions
Step 1: Load labels (fetch or use cache)
Read .opennote/opennote-labels-cache.json. If it exists and was fetched less than 24 hours ago, use the cached labels.
Expected cache format:
CODEBLOCK2
If the cache is missing, empty, or stale (older than 24 hours), fetch labels from the API:
CODEBLOCK3
Response:
CODEBLOCK4
After fetching, write the result to .opennote/opennote-labels-cache.json with the current timestamp as fetchedAt.
Step 2: Choose a label/category
- - If the user specified a label by name or ID, use that
categoryID. - If the user did NOT specify a label, pick a random label from the cached label list.
- If no labels are cached at all, use
category: 0 (no label).
When picking a random label, tell the user which label was selected.
Step 3: Gather note content from the user
Ask the user what they want to write about. Collect:
- - The note text content (required)
- Optional: a title
- Optional: whether they want stickers added
- Optional: whether they want images (user must provide image files)
Step 4: Generate timestamps and fileName
CODEBLOCK5
Step 5: Build the richContent (Quill Delta JSON string)
Build a Quill Delta array of ops, then JSON.stringify() it into the richContent field.
Supported Quill Delta operations
Plain text:
CODEBLOCK6
Newline (required - every Delta must end with at least one):
CODEBLOCK7
Inline styles (on text insert):
- -
bold (boolean) - INLINECODE23 (boolean)
- INLINECODE24 (boolean)
- INLINECODE25 (boolean)
- INLINECODE26 (hex string like
"#2a7fff") — use the palette below for best results - INLINECODE28 (number, typically 2-99; app default is 17)
CODEBLOCK8
Available color palette (36 colors):
Always store the light-mode hex — the app remaps it automatically when rendering in dark mode.
| Name | Light-mode hex |
|---|
| Black | INLINECODE29 |
| Charcoal |
#545454 |
| Dark Grey |
#616161 |
| Warm Slate |
#455a64 |
| Crimson |
#b71c1c |
| Deep Red |
#c0392b |
| Brick |
#bf360c |
| Dark Amber |
#a04000 |
| Mustard |
#9a6e00 |
| Dark Olive |
#558b2f |
| Forest Green |
#2e7d32 |
| Deep Gold |
#8b6914 |
| Dark Teal |
#00695c |
| Dark Cyan |
#00838f |
| Royal Blue |
#1565c0 |
| Blue |
#007aff |
| Indigo |
#5856d6 |
| Deep Purple |
#6a1b9a |
| Magenta |
#e91e63 |
| Pink |
#ff2d55 |
| Berry |
#880e4f |
| Plum |
#6a0572 |
| Dark Violet |
#4a148c |
| Brown |
#8d6e63 |
| Red |
#ff3b30 |
| Dark Rose |
#ad1457 |
| Terracotta |
#8b3a1f |
| Sea Green |
#007a63 |
| Midnight |
#1a237e |
| Deep Sage |
#2e5902 |
| Ochre |
#7a5c00 |
| Steel Grey |
#37474f |
| Wine |
#8b0000 |
| Mahogany |
#6d2f1f |
| Deep Sea |
#005b72 |
| Dark Coffee |
#4e342e |
Block/line styles (attached to the newline op after the line):
- -
align: "left", "center", INLINECODE68 - INLINECODE69 : integer
- INLINECODE70 :
"ordered", "bullet", "checked", INLINECODE74 - INLINECODE75 : INLINECODE76
- INLINECODE77 : INLINECODE78
CODEBLOCK9
Image embed:
CODEBLOCK10
If dimensions are unknown:
CODEBLOCK11
Collage embed (multi-image layout):
CODEBLOCK12
Available collage layouts: twoHorizontal (2), bigLeft2Right (3), bigRight2Left (3), threeRow (3), bigLeft2RectRight (3), bigTop2Bottom (3), bigLeftTopRect2Bottom (4), grid2x2 (4)
Divider embed:
CODEBLOCK13
Example richContent value
The API field richContent must be a JSON string (not an object):
CODEBLOCK14
Step 6: Build the content field (plain text)
INLINECODE88 is a plain-text summary used for home screen preview and search. Strip all formatting — just include the text. Do NOT put JSON or markdown here.
Step 7: Build stickerData (optional)
INLINECODE89 is a JSON string (not an object) containing an array of sticker overlay objects. Stickers float on top of the note page and are NOT part of richContent.
Each sticker object:
CODEBLOCK15
Field rules:
- -
id: unique string, format INLINECODE92 - INLINECODE93 : must be an exact match from the bundled sticker list below
- INLINECODE94 ,
dy: pixel offsets from top-left of note canvas (typical canvas ~390px wide) - INLINECODE96 ,
normalizedDy: proportional position (0.0–1.0 for dx; dy can exceed 1.0 for long content) - INLINECODE98 : 0.3 to 4.0 (1.0 = default 70px base size)
- INLINECODE99 : radians
Example stickerData field value:
CODEBLOCK16
Available sticker asset paths
Kawaii:
CODEBLOCK17
Animals: assets/stickers/animals/001-crocodile.svg through 040-hippopotamus.svg (040 total)
Nature: assets/stickers/nature/001-sunflower.svg through 024-tree.svg (024 total)
Characters: assets/stickers/characters/girl_001-girl.svg through girl_020-girl.svg, hippie_001-hippie.svg through INLINECODE108
Food: assets/stickers/food/misc_001-meat.svg through misc_020-dolphin.svg, k760_001-cat.svg through k760_020-ice_cream.svg, k791_001-cat.svg through INLINECODE114
Cute Life: assets/stickers/cute_life/k678_001-badge.svg through k678_020-vinyl_record.svg, k612_001-teddy_bear.svg through k612_020-yogurt.svg, k155_001-egg_and_bacon.svg through INLINECODE120
Everyday: assets/stickers/everyday/k448_001-cake.svg through k448_020-violin.svg, k450_001-backpack.svg through INLINECODE124
Step 8: Upload images (if any)
If the note includes images, upload each one first:
CODEBLOCK18
Response:
CODEBLOCK19
Use the returned imageName in imageList, richContent image embeds, and optionally diaryCoverImageName.
Image constraints:
- - Supported types: jpg, jpeg, png, gif, webp, heic, heif
- Max size: 15 MB per image
- User storage quota applies
Step 9: Assemble and send the API request
CODEBLOCK20
If the API returns 409 ALREADY_EXISTS, retry with PUT:
CODEBLOCK21
Step 10: Record to note history
After a successful create or update, append an entry to .opennote/opennote-history.json.
Read the existing file first (or start with an empty array [] if it doesn't exist), then append and write back.
Each history entry format:
CODEBLOCK22
Rules for the history:
- -
action: either "created" or INLINECODE134 - INLINECODE135 : first 150 characters of the plain text INLINECODE136
- INLINECODE137 : look up from cached labels, or
"No Label" if category is 0 - INLINECODE139 : true if stickerData was non-null
- INLINECODE140 : true if imageList is non-empty
- For updates, add a new entry with
action: "updated" (do not overwrite previous entries)
Step 11: Confirm to the user
Tell the user:
- - The note was created/updated successfully
- The fileName
- Which label was used (and whether it was randomly selected)
- A brief summary of the content
Reading Notes from the API
When the user asks to read, search, or use an existing note as a template, use the read endpoints (requires diaries:read scope on the token).
Note: The diaries:read scope may not yet be available when creating tokens from the app. If you get a 403 INSUFFICIENT_SCOPE error on read endpoints, the token doesn't have this scope. Writing notes and fetching labels will still work.
List notes
CODEBLOCK23
Query parameters (all optional):
- -
category — filter by categoryID (integer) - INLINECODE146 — search in content and title (partial match)
- INLINECODE147 — results per page, 1–200, default 50
- INLINECODE148 — pagination offset, default 0
Response:
CODEBLOCK24
Get a single note
CODEBLOCK25
Looking Up Previous Notes
When the user asks about notes they've previously written (e.g., "what did I write last time?", "find my note about X"), read .opennote/opennote-history.json. You can match by:
- -
title (partial match) - INLINECODE151 (keyword search)
- INLINECODE152 (label name)
- INLINECODE153 (date range)
- INLINECODE154 (exact match)
Report matching entries with their title, content preview, date, and label.
Validation Rules
Before sending, validate:
- 1.
richContent must be either JSON null or a JSON string that parses into an array where every element has an insert key. - Never put plain text, markdown,
"NULL", "null", or empty string "" into richContent. - INLINECODE162 must be plain text (for preview/search), not JSON.
- INLINECODE163 must contain filename-only entries and include all image filenames from
richContent embeds. - Use Unix milliseconds for
time and lastModified. - INLINECODE167 must be either omitted,
null, or a JSON string parsing to an array of valid sticker objects. - Each sticker must use an exact
assetPath from the bundled sticker list above. - Sticker
scale should be between 0.3 and 4.0.
通过OpenNote公共API写笔记
使用 https://api.opennote.cc/api/v1 的公共API向OpenNote写笔记。
开始使用
你需要准备
- 1. 一个 OpenNote账户 — 从 App Store 下载应用
- 一个 Pro订阅 — API访问是Pro功能
- 一个 个人访问令牌(PAT) — 从应用中生成
生成API令牌
- 1. 打开OpenNote应用
- 前往 个人资料 → API令牌
- 点击 创建令牌
- 选择你需要的权限范围:
- diaries:write — 创建和更新笔记
- images:write — 上传图片到笔记
- 5. 设置过期时间(默认:90天,最长:365天,或永不过期)
- 立即复制令牌 — 它以 milopatv1_ 开头,且 仅显示一次
设置环境变量
在你的OpenClaw环境中设置 OPENNOTEAPITOKEN:
选项A — OpenClaw配置(推荐):
在OpenClaw设置中,添加环境变量:
OPENNOTEAPITOKEN=milopatv1yourtoken_here
选项B — Shell配置文件(如果本地运行):
bash
export OPENNOTEAPITOKEN=milopatv1yourtoken_here
将其添加到 ~/.zshrc 或 ~/.bashrc 以在会话间持久化。
安全性:
- - 切勿将令牌提交到版本控制
- 该令牌授予对笔记的写入权限 — 请像密码一样对待
- 如果泄露,请立即从应用的 个人资料 → API令牌 中撤销
令牌生命周期
- - 令牌根据你设置的时长过期(默认90天)
- 每个账户最多可以有 5个活跃令牌
- 随时从应用中撤销未使用的令牌
- 当令牌过期或被撤销时,生成一个新令牌并更新 OPENNOTEAPITOKEN
- 如果收到 401 INVALIDAPITOKEN 错误,说明令牌已过期或被撤销
前提条件
令牌从 OPENNOTEAPITOKEN 环境变量中读取。它必须具有 diaries:write 权限范围。如果要上传图片,还需要 images:write 权限。
本地缓存文件
此技能在工作目录下的 .opennote/ 中维护两个本地文件:
- - 标签缓存:.opennote/opennote-labels-cache.json — 缓存的用户标签
- 笔记历史:.opennote/opennote-history.json — 通过此技能编写/编辑的所有笔记的日志
每次调用开始时始终读取这些文件。如果它们缺失,则视为空。如果用户询问之前编写的笔记,请查阅历史文件。
操作说明
步骤1:加载标签(获取或使用缓存)
读取 .opennote/opennote-labels-cache.json。如果存在且获取时间不超过24小时,则使用缓存的标签。
预期缓存格式:
json
{
fetchedAt: 1741111111111,
labels: [
{ categoryID: 1, categoryName: 个人, categoryColor: 4294198070 },
{ categoryID: 2, categoryName: 工作, categoryColor: 4283215696 }
]
}
如果缓存缺失、为空或过期(超过24小时),则从API获取标签:
bash
curl -s https://api.opennote.cc/api/v1/labels \
-H Authorization: Bearer $OPENNOTEAPITOKEN
响应:
json
{
labels: [
{ categoryID: 1, categoryName: 个人, categoryColor: 4294198070, visibility: true, coverImageName: null, lastModified: 1741111111111, fontFamily: null, backgroundPreset: null }
]
}
获取后,将结果写入 .opennote/opennote-labels-cache.json,并将当前时间戳作为 fetchedAt。
步骤2:选择标签/分类
- - 如果用户通过名称或ID指定了标签,则使用该 categoryID。
- 如果用户没有指定标签,则从缓存的标签列表中随机选择一个标签。
- 如果根本没有缓存的标签,则使用 category: 0(无标签)。
当随机选择标签时,告知用户选择了哪个标签。
步骤3:收集用户的笔记内容
询问用户想写什么。收集:
- - 笔记文本内容(必需)
- 可选:标题
- 可选:是否添加贴纸
- 可选:是否包含图片(用户必须提供图片文件)
步骤4:生成时间戳和文件名
js
const now = Date.now(); // Unix毫秒
const fileName = ${now}.json;
步骤5:构建richContent(Quill Delta JSON字符串)
构建一个Quill Delta操作数组,然后使用 JSON.stringify() 将其转换为 richContent 字段。
支持的Quill Delta操作
纯文本:
json
{ insert: Hello world }
换行(必需 — 每个Delta必须至少以一个换行结束):
json
{ insert: \n }
内联样式(在文本插入上):
- - bold(布尔值)
- italic(布尔值)
- underline(布尔值)
- strike(布尔值)
- color(十六进制字符串如 #2a7fff)— 使用下面的调色板以获得最佳效果
- size(数字,通常2-99;应用默认值为17)
json
{ insert: 重要, attributes: { bold: true, color: #ff0000, size: 20 } }
可用颜色调色板(36种颜色):
始终存储浅色模式十六进制 — 应用在深色模式下渲染时会自动重新映射。
#545454 |
| 深灰色 | #616161 |
| 暖石板色 | #455a64 |
| 深红 | #b71c1c |
| 深红 | #c0392b |
| 砖红色 | #bf360c |
| 深琥珀色 | #a04000 |
| 芥末色 | #9a6e00 |
| 深橄榄色 | #558b2f |
| 森林绿 | #2e7d32 |
| 深金色 | #8b6914 |
| 深青色 | #00695c |
| 深青色 | #00838f |
| 皇家蓝 | #1565c0 |
| 蓝色 | #007aff |
| 靛蓝 | #5856d6 |
| 深紫色 | #6a1b9a |
| 品红 | #e91e63 |
| 粉色 | #ff2d55 |
| 浆果色 | #880e4f |
| 李子色 | #6a0572 |
| 深紫罗兰 | #4a148c |
| 棕色 | #8d6e63 |
| 红色 | #ff3b30 |
| 深玫瑰色 | #ad1457 |
| 赤陶色 | #8b3a1f |
| 海绿色 | #007a63 |
| 午夜蓝 | #1a237e |
| 深鼠尾草 | #2e5902 |
| 赭色 | #7a5c00 |
| 钢灰色 | #37474f |
| 酒红色 | #8b0000 |
| 桃花心木色 | #6d2f1f |
| 深海色 | #005b72 |
| 深咖啡色 | #4e342e |
块/行样式(附加在行后的换行操作上):
- - align:left、center、right
- indent:整数
- list:ordered、bullet、checked、unchecked
- code-block:true
- blockquote:true
json
{ insert: 一个项目符号点 },
{ insert: \n, attributes: { list: bullet } }
图片嵌入:
json
{ insert: {