Why a terminal client
Microsoft does not ship OneNote for Linux, and many workplaces will not let you install it at all. The web app covers reading, but it is a poor fit for the thing people actually want from a shell: capturing what just happened without leaving the shell.
Linux desktops
The one supported way to put a page in your own OneNote notebook from Ubuntu, Fedora, Arch, or anything else, without a browser.
Servers over SSH
Pipe a log, a diff, or a failing test into a OneNote page from a machine that has no graphical session at all.
Locked-down work machines
If you can install a Node package, you can capture notes. There is nothing to install with administrator rights and no browser extension.
Scripts and cron
Stable exit codes and --json output make it safe to call from automation that has to react when something fails.
Install
The client is published to npm as onenotesystem ↗, and its source is at onenote-terminal ↗. It needs Node.js 18.17 or newer and has no other dependencies.
npm install -g onenotesystemThat installs two commands: onenotesystem and the shorter alias ons. To try it without installing anything, run npx onenotesystem --help.
Getting help
Every command documents itself, so you should rarely need this page once it is installed. --help and -h work at the top level and on each command, and help <command> does the same thing.
onenotesystem --help # commands, configuration, exit codes
onenotesystem capture --help # every flag capture accepts, with examples
onenotesystem help append # the same, spelled the other wayAliases resolve too, so onenotesystem new -h shows the capture help.
The client is not a OneNote account. It talks to your OneNote System deployment, which holds your Microsoft connection. If you have not deployed one yet, start with the guided setup.
Configure
You need your deployment address and an API key from Setup Step 5 — the same key a Shortcut would use. One key can be shared by both, though a separate key per device is easier to revoke.
$ onenotesystem configure
OneNote System deployment URL: https://my-onenote-system.vercel.app
API key (input hidden):
Checking the deployment...
Reached onenote-system.
Saved configuration to /home/you/.config/onenotesystem/config.jsonThe key is saved to a file only your user account can read: ~/.config/onenotesystem/config.json on Linux and macOS, or %APPDATA%\onenotesystem\config.json on Windows. It is never printed back to the terminal.
Configuring without prompts
Pass the values as flags when you are setting up a server or a container:
onenotesystem configure --url https://my-onenote-system.vercel.app --api-key "$KEY" --default-page "Quick Inbox"Or skip the file entirely. Environment variables take precedence over it, which is what you want in CI:
| Variable | Purpose |
|---|---|
ONENOTE_URL | Your deployment address, with no slash at the end |
ONENOTE_API_KEY | An API key from Setup Step 5 |
ONENOTE_DEFAULT_PAGE | Page title append uses when you do not name one |
ONENOTE_TIMEOUT_MS | Request timeout, default 15000 |
ONENOTE_CONFIG_DIR | Override where the config file lives |
Check that it works
doctor tests the deployment, its database, and your API key separately, so a failure tells you which one is wrong.
$ onenotesystem doctor
Deployment URL: https://my-onenote-system.vercel.app (from config file)
API key: set (from config file)
Default page: Quick Inbox (from config file)
Service: onenote-system
Database: reachable
Key: acceptedCreate a page
capture creates a new page in the OneNote section you chose during setup. It calls POST /api/capture — the same endpoint as the Capture to OneNote Shortcut.
# A page with just a title
onenotesystem capture "Standup notes"
# A page with a body
onenotesystem capture "Standup notes" --content "Shipped the CLI"
# A page from a file, with a source link
onenotesystem capture "Article" --file notes.md --url https://example.com/article
# A page from whatever another command printed
git log -1 --stat | onenotesystem capture "Today's commit" --stdin| Flag | Meaning |
|---|---|
--title <text> | Title, if you would rather not use positional words |
--content <text> | Page body |
--file <path> | Read the body from a file |
--stdin | Read the body from piped input |
--url <link> | Record a source link under the body |
--json | Print the deployment's JSON response instead of a summary |
Pages carry a title, plain text, and an optional source link — the same small, reliable format the Shortcuts use. Line breaks are preserved. Markdown is not rendered; it arrives as literal text. Content is limited to 100,000 characters and titles to 200, both checked before anything is uploaded.
Append to a page
append adds text to a page that already exists — a running inbox, a daily log, a project page. It calls POST /api/append.
# Add to a page by its exact title
onenotesystem append "Ask about the Q3 budget" --page-title "Quick Inbox"
# Add to a page by ID, from any section
onenotesystem append "Deploy finished" --page-id "1-abc123!..."
# Add whatever another command printed
dmesg | tail -20 | onenotesystem append --stdin --page-title "Server log"
# Set a default page once, then leave the flag off
onenotesystem configure --default-page "Quick Inbox"
onenotesystem append "A thought"--page-title must match a page title exactly, and that page must be in your configured default section. If two pages share the title the command stops and asks for --page-id, because quietly appending to the wrong one would be worse than failing. A page ID can reach any section the connected Microsoft account can see.
Microsoft To Do
todo creates and completes Microsoft To Do tasks through the same deployment and the same API key. Calls /api/todo.
# Add a task to your default To Do list
onenotesystem todo add "Renew the domain" --due +7d
# A specific list, with a note and a reminder
onenotesystem todo add "Call the bank" --list Errands --due tomorrow --note "Ask about the fee"
# See what is open, then close one out
onenotesystem todo list
onenotesystem todo done AAMkAG...| Flag | Meaning |
|---|---|
--list <name> | Which To Do list to use; defaults to your default list |
--due <when> | 2026-09-15, today, tomorrow, +3d, +2w, or 2026-09-15T14:30 |
--reminder <when> | Same formats; also switches the reminder on |
--note <text> | A longer note on the task |
--all | On todo list, include completed tasks |
--top <n> | On todo list, how many to show (default 25, max 100) |
Dates are sent with your computer's time zone, so a bare date stays the day you meant rather than shifting for anyone west of UTC. todo list prints each task's ID underneath it, which is what todo done takes.
To Do needs the Tasks.ReadWrite permission on your Microsoft app registration. If you added it after connecting, reconnect Microsoft in setup — a permission added later does not upgrade a connection that was already approved. OneNote keeps working throughout, and the client tells you plainly when this is the problem.
Scripting
Progress messages go to stderr and --json goes to stdout alone, so onenotesystem capture … --json | jq always works.
#!/usr/bin/env bash
# Log a failed nightly build to OneNote without opening a browser.
if ! make nightly > build.log 2>&1; then
onenotesystem capture "Nightly build failed $(date -I)" --file build.log
fiEvery command returns a stable exit code, so a script can tell an expired key apart from a dropped connection:
| Code | Meaning |
|---|---|
0 | Success |
1 | Usage error — bad flags, missing input, failed local validation |
2 | Unauthorized — the deployment rejected the API key |
3 | Not found — no page matched the title or ID |
4 | Network — DNS, TLS, timeout, or connection refused |
5 | A deployment error the client cannot classify further |
6 | Not configured — no deployment URL or API key |
7 | Conflict — no default section, or more than one page has that title |
if ! onenotesystem append "$LINE" --page-title "Log"; then
case $? in
2) echo "Key expired; run onenotesystem configure" ;;
4) echo "Offline; queueing locally" ;;
esac
fiSecurity
Your API key is sent only to the deployment address you configured, as an Authorization: Bearer header. It is never printed, logged, or included in an error message — including the unauthorized one. The config file is written with owner-only permissions.
Deployment addresses must use HTTPS. Plain http:// is accepted only for localhost and 127.0.0.1, so a typo cannot quietly send your key over an unencrypted connection.
Your note content passes through your own deployment to Microsoft Graph and nowhere else. The client has no telemetry and no runtime dependencies.
On a shared machine, prefer an environment variable to the config file, and create a key you can revoke for that machine alone. Anyone who can read the file can write to your OneNote section.
Terminal troubleshooting
Exit code 6, “not configured”
No deployment URL or API key was found. Run onenotesystem configure, or set ONENOTE_URL and ONENOTE_API_KEY.
Exit code 2, “unauthorized”
The key was rejected. Keys are shown only once at creation; if the original was lost, create a new one in Setup Step 5 and run configure again.
Exit code 7, “no default OneNote section”
Return to Setup Step 4, load your notebooks, and choose the section that should receive new pages.
Exit code 3, “no page titled …”
Titles must match exactly, including capitalisation, and the page must be in your default section. Use --page-id to reach a page in another section.
Exit code 4, “could not reach”
Run onenotesystem doctor. If it also fails, open your deployment address in a browser before changing anything about the client.
command not found after install
npm's global bin directory is not on your PATH. Run npm config get prefix and add that directory's bin to your shell profile.
For deployment and Microsoft errors, continue with the full troubleshooting guide →